Revert "Add ringrtc support."
Revert the following commits: "Handle busy call while in PSTN call." Commit23a0bb3ce0
. "Add ringrtc support." Commit3ac540c687
.
This commit is contained in:
parent
72662b5b52
commit
97cc8b7777
12 changed files with 3140 additions and 781 deletions
|
@ -88,7 +88,7 @@ dependencies {
|
|||
|
||||
implementation 'org.whispersystems:signal-service-android:2.13.7'
|
||||
|
||||
implementation 'org.signal:ringrtc-android:0.1.1'
|
||||
implementation 'org.whispersystems:webrtc-android:M75'
|
||||
|
||||
implementation "me.leolin:ShortcutBadger:1.1.16"
|
||||
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||
|
@ -198,7 +198,7 @@ dependencyVerification {
|
|||
'org.conscrypt:conscrypt-android:400ca559a49b860a82862b22cee0e3110764bdcf7ee7c79e7479895c25cdfc09',
|
||||
'org.signal:aesgcmprovider:6eb4422e8a618b3b76cb2096a3619d251f9e27989dc68307a1e5414c3710f2d1',
|
||||
'org.whispersystems:signal-service-android:5115aa434c52ca671c513995e6ae67d73f3abaaa605f9e6cf64c2e01da961c7e',
|
||||
'org.signal:ringrtc-android:91d4d89847c10e718bc8badfa0353f0095678855c35c2575509fea97f615de86',
|
||||
'org.whispersystems:webrtc-android:f8231bb57923afb243760213dc58924e85cce42f2f3cc8cb33a6d883672a921a',
|
||||
'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774',
|
||||
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
|
||||
'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',
|
||||
|
|
3
proguard-webrtc.pro
Normal file
3
proguard-webrtc.pro
Normal file
|
@ -0,0 +1,3 @@
|
|||
-dontwarn org.webrtc.NetworkMonitorAutoDetect
|
||||
-dontwarn android.net.Network
|
||||
-keep class org.webrtc.** { *; }
|
|
@ -33,7 +33,6 @@ import com.google.android.gms.security.ProviderInstaller;
|
|||
|
||||
import org.conscrypt.Conscrypt;
|
||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||
import org.signal.ringrtc.CallConnectionFactory;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
|
@ -71,6 +70,8 @@ import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
|||
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
import org.webrtc.PeerConnectionFactory.InitializationOptions;
|
||||
import org.webrtc.voiceengine.WebRtcAudioManager;
|
||||
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
||||
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
||||
|
@ -125,7 +126,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||
initializeSignedPreKeyCheck();
|
||||
initializePeriodicTasks();
|
||||
initializeCircumvention();
|
||||
initializeRingRtc();
|
||||
initializeWebRtc();
|
||||
initializePendingMessages();
|
||||
initializeUnidentifiedDeliveryAbilityRefresh();
|
||||
initializeBlobProvider();
|
||||
|
@ -281,7 +282,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||
}
|
||||
}
|
||||
|
||||
private void initializeRingRtc() {
|
||||
private void initializeWebRtc() {
|
||||
try {
|
||||
Set<String> HARDWARE_AEC_BLACKLIST = new HashSet<String>() {{
|
||||
add("Pixel");
|
||||
|
@ -310,7 +311,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
|
||||
}
|
||||
|
||||
CallConnectionFactory.initialize(this);
|
||||
PeerConnectionFactory.initialize(InitializationOptions.builder(this).createInitializationOptions());
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
|
|
@ -43,10 +43,10 @@ import org.thoughtcrime.securesms.R;
|
|||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.VerifySpan;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.webrtc.CameraState;
|
||||
import org.webrtc.SurfaceViewRenderer;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||
import org.thoughtcrime.securesms.webrtc.CameraState;
|
||||
import org.webrtc.SurfaceViewRenderer;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
|
||||
|
|
|
@ -1,293 +0,0 @@
|
|||
package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.signal.ringrtc.CallConnection;
|
||||
import org.signal.ringrtc.CallConnectionFactory;
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.SignalMessageRecipient;
|
||||
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.webrtc.AudioSource;
|
||||
import org.webrtc.AudioTrack;
|
||||
import org.webrtc.Camera1Enumerator;
|
||||
import org.webrtc.Camera2Enumerator;
|
||||
import org.webrtc.CameraEnumerator;
|
||||
import org.webrtc.CameraVideoCapturer;
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.IceCandidate;
|
||||
import org.webrtc.MediaConstraints;
|
||||
import org.webrtc.MediaStream;
|
||||
import org.webrtc.SurfaceTextureHelper;
|
||||
import org.webrtc.VideoSink;
|
||||
import org.webrtc.VideoSource;
|
||||
import org.webrtc.VideoTrack;
|
||||
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.BACK;
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.FRONT;
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.NONE;
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.PENDING;
|
||||
|
||||
public class CallConnectionWrapper {
|
||||
private static final String TAG = Log.tag(CallConnectionWrapper.class);
|
||||
|
||||
@NonNull private final CallConnection callConnection;
|
||||
@NonNull private final AudioTrack audioTrack;
|
||||
@NonNull private final AudioSource audioSource;
|
||||
@NonNull private final Camera camera;
|
||||
@Nullable private final VideoSource videoSource;
|
||||
@Nullable private final VideoTrack videoTrack;
|
||||
|
||||
public CallConnectionWrapper(@NonNull Context context,
|
||||
@NonNull CallConnectionFactory factory,
|
||||
@NonNull CallConnection.Observer observer,
|
||||
@NonNull VideoSink localRenderer,
|
||||
@NonNull CameraEventListener cameraEventListener,
|
||||
@NonNull EglBase eglBase,
|
||||
boolean hideIp,
|
||||
long callId,
|
||||
boolean outBound,
|
||||
@NonNull SignalMessageRecipient recipient,
|
||||
@NonNull SignalServiceAccountManager accountManager)
|
||||
throws UnregisteredUserException, IOException, CallException
|
||||
{
|
||||
|
||||
CallConnection.Configuration configuration = new CallConnection.Configuration(callId,
|
||||
outBound,
|
||||
recipient,
|
||||
accountManager,
|
||||
hideIp);
|
||||
|
||||
this.callConnection = factory.createCallConnection(configuration, observer);
|
||||
this.callConnection.setAudioPlayout(false);
|
||||
this.callConnection.setAudioRecording(false);
|
||||
|
||||
MediaStream mediaStream = factory.createLocalMediaStream("ARDAMS");
|
||||
MediaConstraints audioConstraints = new MediaConstraints();
|
||||
|
||||
audioConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
|
||||
this.audioSource = factory.createAudioSource(audioConstraints);
|
||||
this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
|
||||
this.audioTrack.setEnabled(false);
|
||||
mediaStream.addTrack(audioTrack);
|
||||
|
||||
this.camera = new Camera(context, cameraEventListener);
|
||||
|
||||
if (camera.capturer != null) {
|
||||
this.videoSource = factory.createVideoSource(false);
|
||||
this.videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
|
||||
|
||||
camera.capturer.initialize(SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.getEglBaseContext()), context, videoSource.getCapturerObserver());
|
||||
|
||||
this.videoTrack.addSink(localRenderer);
|
||||
this.videoTrack.setEnabled(false);
|
||||
mediaStream.addTrack(videoTrack);
|
||||
} else {
|
||||
this.videoSource = null;
|
||||
this.videoTrack = null;
|
||||
}
|
||||
|
||||
this.callConnection.addStream(mediaStream);
|
||||
}
|
||||
|
||||
public boolean addIceCandidate(IceCandidate candidate) {
|
||||
return callConnection.addIceCandidate(candidate);
|
||||
}
|
||||
|
||||
public void sendOffer() throws CallException {
|
||||
callConnection.sendOffer();
|
||||
}
|
||||
|
||||
public boolean validateResponse(SignalMessageRecipient recipient, Long inCallId)
|
||||
throws CallException
|
||||
{
|
||||
return callConnection.validateResponse(recipient, inCallId);
|
||||
}
|
||||
|
||||
public void handleOfferAnswer(String sessionDescription) throws CallException {
|
||||
callConnection.handleOfferAnswer(sessionDescription);
|
||||
}
|
||||
|
||||
public void acceptOffer(String offer) throws CallException {
|
||||
callConnection.acceptOffer(offer);
|
||||
}
|
||||
|
||||
public void hangUp() throws CallException {
|
||||
callConnection.hangUp();
|
||||
}
|
||||
|
||||
public void answerCall() throws CallException {
|
||||
callConnection.answerCall();
|
||||
}
|
||||
|
||||
public void sendBusy(SignalMessageRecipient recipient, Long inCallId) throws CallException
|
||||
{
|
||||
callConnection.sendBusy(recipient, inCallId);
|
||||
}
|
||||
|
||||
public void setVideoEnabled(boolean enabled) throws CallException {
|
||||
if (videoTrack != null) {
|
||||
videoTrack.setEnabled(enabled);
|
||||
}
|
||||
camera.setEnabled(enabled);
|
||||
callConnection.sendVideoStatus(enabled);
|
||||
}
|
||||
|
||||
public void flipCamera() {
|
||||
camera.flip();
|
||||
}
|
||||
|
||||
public CameraState getCameraState() {
|
||||
return new CameraState(camera.getActiveDirection(), camera.getCount());
|
||||
}
|
||||
|
||||
public void setCommunicationMode() {
|
||||
callConnection.setAudioPlayout(true);
|
||||
callConnection.setAudioRecording(true);
|
||||
}
|
||||
|
||||
public void setAudioEnabled(boolean enabled) {
|
||||
audioTrack.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
camera.dispose();
|
||||
|
||||
if (videoSource != null) {
|
||||
videoSource.dispose();
|
||||
}
|
||||
|
||||
audioSource.dispose();
|
||||
callConnection.dispose();
|
||||
}
|
||||
|
||||
private static class Camera implements CameraVideoCapturer.CameraSwitchHandler {
|
||||
|
||||
@Nullable
|
||||
private final CameraVideoCapturer capturer;
|
||||
private final CameraEventListener cameraEventListener;
|
||||
private final int cameraCount;
|
||||
|
||||
private CameraState.Direction activeDirection;
|
||||
private boolean enabled;
|
||||
|
||||
Camera(@NonNull Context context, @NonNull CameraEventListener cameraEventListener)
|
||||
{
|
||||
this.cameraEventListener = cameraEventListener;
|
||||
CameraEnumerator enumerator = getCameraEnumerator(context);
|
||||
cameraCount = enumerator.getDeviceNames().length;
|
||||
|
||||
CameraVideoCapturer capturerCandidate = createVideoCapturer(enumerator, FRONT);
|
||||
if (capturerCandidate != null) {
|
||||
activeDirection = FRONT;
|
||||
} else {
|
||||
capturerCandidate = createVideoCapturer(enumerator, BACK);
|
||||
if (capturerCandidate != null) {
|
||||
activeDirection = BACK;
|
||||
} else {
|
||||
activeDirection = NONE;
|
||||
}
|
||||
}
|
||||
capturer = capturerCandidate;
|
||||
}
|
||||
|
||||
void flip() {
|
||||
if (capturer == null || cameraCount < 2) {
|
||||
throw new AssertionError("Tried to flip the camera, but we only have " + cameraCount +
|
||||
" of them.");
|
||||
}
|
||||
activeDirection = PENDING;
|
||||
capturer.switchCamera(this);
|
||||
}
|
||||
|
||||
void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
|
||||
if (capturer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (enabled) {
|
||||
capturer.startCapture(1280, 720, 30);
|
||||
} else {
|
||||
capturer.stopCapture();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Got interrupted while trying to stop video capture", e);
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
if (capturer != null) {
|
||||
capturer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return cameraCount;
|
||||
}
|
||||
|
||||
@NonNull CameraState.Direction getActiveDirection() {
|
||||
return enabled ? activeDirection : NONE;
|
||||
}
|
||||
|
||||
@Nullable CameraVideoCapturer getCapturer() {
|
||||
return capturer;
|
||||
}
|
||||
|
||||
private @Nullable CameraVideoCapturer createVideoCapturer(@NonNull CameraEnumerator enumerator,
|
||||
@NonNull CameraState.Direction direction)
|
||||
{
|
||||
String[] deviceNames = enumerator.getDeviceNames();
|
||||
for (String deviceName : deviceNames) {
|
||||
if ((direction == FRONT && enumerator.isFrontFacing(deviceName)) ||
|
||||
(direction == BACK && enumerator.isBackFacing(deviceName)))
|
||||
{
|
||||
return enumerator.createCapturer(deviceName, null);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private @NonNull CameraEnumerator getCameraEnumerator(@NonNull Context context) {
|
||||
boolean camera2EnumeratorIsSupported = false;
|
||||
try {
|
||||
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context);
|
||||
} catch (final Throwable throwable) {
|
||||
Log.w(TAG, "Camera2Enumator.isSupport() threw.", throwable);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Camera2 enumerator supported: " + camera2EnumeratorIsSupported);
|
||||
|
||||
return camera2EnumeratorIsSupported ? new Camera2Enumerator(context)
|
||||
: new Camera1Enumerator(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraSwitchDone(boolean isFrontFacing) {
|
||||
activeDirection = isFrontFacing ? FRONT : BACK;
|
||||
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraSwitchError(String errorMessage) {
|
||||
Log.e(TAG, "onCameraSwitchError: " + errorMessage);
|
||||
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
|
||||
}
|
||||
}
|
||||
|
||||
public interface CameraEventListener {
|
||||
void onCameraSwitchCompleted(@NonNull CameraState newCameraState);
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.signal.ringrtc.SignalMessageRecipient;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
public final class MessageRecipient implements SignalMessageRecipient {
|
||||
|
||||
private static final String TAG = Log.tag(MessageRecipient.class);
|
||||
|
||||
@NonNull private final Recipient recipient;
|
||||
@NonNull private final SignalServiceMessageSender messageSender;
|
||||
|
||||
public MessageRecipient(SignalServiceMessageSender messageSender,
|
||||
Recipient recipient)
|
||||
{
|
||||
this.recipient = recipient;
|
||||
this.messageSender = messageSender;
|
||||
}
|
||||
|
||||
public @NonNull Address getAddress() {
|
||||
return recipient.getAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(@NonNull SignalMessageRecipient o) {
|
||||
if (!(o instanceof MessageRecipient)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MessageRecipient that = (MessageRecipient) o;
|
||||
|
||||
return recipient.equals(that.recipient);
|
||||
}
|
||||
|
||||
void sendMessage(Context context, SignalServiceCallMessage callMessage)
|
||||
throws IOException, UntrustedIdentityException, IOException
|
||||
{
|
||||
messageSender.sendCallMessage(new SignalServiceAddress(recipient.getAddress().toPhoneString()),
|
||||
UnidentifiedAccessUtil.getAccessFor(context, recipient),
|
||||
callMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendOfferMessage(Context context, long callId, String description)
|
||||
throws IOException, UntrustedIdentityException, IOException
|
||||
{
|
||||
Log.i(TAG, "MessageRecipient::sendOfferMessage(): callId: " + callId);
|
||||
|
||||
OfferMessage offerMessage = new OfferMessage(callId, description);
|
||||
sendMessage(context, SignalServiceCallMessage.forOffer(offerMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendAnswerMessage(Context context, long callId, String description)
|
||||
throws IOException, UntrustedIdentityException, IOException
|
||||
{
|
||||
Log.i(TAG, "MessageRecipient::sendAnswerMessage(): callId: " + callId);
|
||||
|
||||
AnswerMessage answerMessage = new AnswerMessage(callId, description);
|
||||
sendMessage(context, SignalServiceCallMessage.forAnswer(answerMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendIceUpdates(Context context, List<IceUpdateMessage> iceUpdateMessages)
|
||||
throws IOException, UntrustedIdentityException, IOException
|
||||
{
|
||||
Log.i(TAG, "MessageRecipient::sendIceUpdates(): iceUpdates: " + iceUpdateMessages.size());
|
||||
|
||||
sendMessage(context, SignalServiceCallMessage.forIceUpdates(iceUpdateMessages));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendHangupMessage(Context context, long callId)
|
||||
throws IOException, UntrustedIdentityException, IOException
|
||||
{
|
||||
Log.i(TAG, "MessageRecipient::sendHangupMessage(): callId: " + callId);
|
||||
|
||||
HangupMessage hangupMessage = new HangupMessage(callId);
|
||||
sendMessage(context, SignalServiceCallMessage.forHangup(hangupMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendBusyMessage(Context context, long callId)
|
||||
throws IOException, UntrustedIdentityException, IOException
|
||||
{
|
||||
Log.i(TAG, "MessageRecipient::sendBusyMessage(): callId: " + callId);
|
||||
|
||||
BusyMessage busyMessage = new BusyMessage(callId);
|
||||
sendMessage(context, SignalServiceCallMessage.forBusy(busyMessage));
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
|||
package org.thoughtcrime.securesms.ringrtc;
|
||||
package org.thoughtcrime.securesms.webrtc;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package org.thoughtcrime.securesms.webrtc;
|
||||
|
||||
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
|
||||
public class PeerConnectionFactoryOptions extends PeerConnectionFactory.Options {
|
||||
|
||||
public PeerConnectionFactoryOptions() {
|
||||
this.networkIgnoreMask = 1 << 4;
|
||||
}
|
||||
}
|
424
src/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.java
Normal file
424
src/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.java
Normal file
|
@ -0,0 +1,424 @@
|
|||
package org.thoughtcrime.securesms.webrtc;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.webrtc.AudioSource;
|
||||
import org.webrtc.AudioTrack;
|
||||
import org.webrtc.Camera1Enumerator;
|
||||
import org.webrtc.Camera2Enumerator;
|
||||
import org.webrtc.CameraEnumerator;
|
||||
import org.webrtc.CameraVideoCapturer;
|
||||
import org.webrtc.DataChannel;
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.IceCandidate;
|
||||
import org.webrtc.MediaConstraints;
|
||||
import org.webrtc.MediaStream;
|
||||
import org.webrtc.PeerConnection;
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
import org.webrtc.SdpObserver;
|
||||
import org.webrtc.SessionDescription;
|
||||
import org.webrtc.SurfaceTextureHelper;
|
||||
import org.webrtc.VideoSink;
|
||||
import org.webrtc.VideoSource;
|
||||
import org.webrtc.VideoTrack;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.BACK;
|
||||
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.FRONT;
|
||||
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.NONE;
|
||||
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.PENDING;
|
||||
|
||||
public class PeerConnectionWrapper {
|
||||
private static final String TAG = PeerConnectionWrapper.class.getSimpleName();
|
||||
|
||||
private static final PeerConnection.IceServer STUN_SERVER = new PeerConnection.IceServer("stun:stun1.l.google.com:19302");
|
||||
|
||||
@NonNull private final PeerConnection peerConnection;
|
||||
@NonNull private final AudioTrack audioTrack;
|
||||
@NonNull private final AudioSource audioSource;
|
||||
@NonNull private final Camera camera;
|
||||
@Nullable private final VideoSource videoSource;
|
||||
@Nullable private final VideoTrack videoTrack;
|
||||
|
||||
public PeerConnectionWrapper(@NonNull Context context,
|
||||
@NonNull PeerConnectionFactory factory,
|
||||
@NonNull PeerConnection.Observer observer,
|
||||
@NonNull VideoSink localRenderer,
|
||||
@NonNull List<PeerConnection.IceServer> turnServers,
|
||||
@NonNull CameraEventListener cameraEventListener,
|
||||
@NonNull EglBase eglBase,
|
||||
boolean hideIp)
|
||||
{
|
||||
List<PeerConnection.IceServer> iceServers = new LinkedList<>();
|
||||
iceServers.add(STUN_SERVER);
|
||||
iceServers.addAll(turnServers);
|
||||
|
||||
MediaConstraints constraints = new MediaConstraints();
|
||||
MediaConstraints audioConstraints = new MediaConstraints();
|
||||
PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers);
|
||||
|
||||
configuration.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
|
||||
configuration.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
|
||||
|
||||
if (hideIp) {
|
||||
configuration.iceTransportsType = PeerConnection.IceTransportsType.RELAY;
|
||||
}
|
||||
|
||||
constraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
|
||||
audioConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
|
||||
|
||||
this.peerConnection = factory.createPeerConnection(configuration, constraints, observer);
|
||||
this.peerConnection.setAudioPlayout(false);
|
||||
this.peerConnection.setAudioRecording(false);
|
||||
|
||||
MediaStream mediaStream = factory.createLocalMediaStream("ARDAMS");
|
||||
this.audioSource = factory.createAudioSource(audioConstraints);
|
||||
this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
|
||||
this.audioTrack.setEnabled(false);
|
||||
mediaStream.addTrack(audioTrack);
|
||||
|
||||
this.camera = new Camera(context, cameraEventListener);
|
||||
|
||||
if (camera.capturer != null) {
|
||||
this.videoSource = factory.createVideoSource(false);
|
||||
this.videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
|
||||
|
||||
camera.capturer.initialize(SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.getEglBaseContext()), context, videoSource.getCapturerObserver());
|
||||
|
||||
this.videoTrack.addSink(localRenderer);
|
||||
this.videoTrack.setEnabled(false);
|
||||
mediaStream.addTrack(videoTrack);
|
||||
} else {
|
||||
this.videoSource = null;
|
||||
this.videoTrack = null;
|
||||
}
|
||||
|
||||
this.peerConnection.addStream(mediaStream);
|
||||
}
|
||||
|
||||
public void setVideoEnabled(boolean enabled) {
|
||||
if (this.videoTrack != null) {
|
||||
this.videoTrack.setEnabled(enabled);
|
||||
}
|
||||
camera.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public void flipCamera() {
|
||||
camera.flip();
|
||||
}
|
||||
|
||||
public CameraState getCameraState() {
|
||||
return new CameraState(camera.getActiveDirection(), camera.getCount());
|
||||
}
|
||||
|
||||
public void setCommunicationMode() {
|
||||
this.peerConnection.setAudioPlayout(true);
|
||||
this.peerConnection.setAudioRecording(true);
|
||||
}
|
||||
|
||||
public void setAudioEnabled(boolean enabled) {
|
||||
this.audioTrack.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public DataChannel createDataChannel(String name) {
|
||||
DataChannel.Init dataChannelConfiguration = new DataChannel.Init();
|
||||
dataChannelConfiguration.ordered = true;
|
||||
|
||||
return this.peerConnection.createDataChannel(name, dataChannelConfiguration);
|
||||
}
|
||||
|
||||
public SessionDescription createOffer(MediaConstraints mediaConstraints) throws PeerConnectionException {
|
||||
final SettableFuture<SessionDescription> future = new SettableFuture<>();
|
||||
|
||||
peerConnection.createOffer(new SdpObserver() {
|
||||
@Override
|
||||
public void onCreateSuccess(SessionDescription sdp) {
|
||||
future.set(sdp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateFailure(String error) {
|
||||
future.setException(new PeerConnectionException(error));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetSuccess() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetFailure(String error) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}, mediaConstraints);
|
||||
|
||||
try {
|
||||
return correctSessionDescription(future.get());
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new PeerConnectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SessionDescription createAnswer(MediaConstraints mediaConstraints) throws PeerConnectionException {
|
||||
final SettableFuture<SessionDescription> future = new SettableFuture<>();
|
||||
|
||||
peerConnection.createAnswer(new SdpObserver() {
|
||||
@Override
|
||||
public void onCreateSuccess(SessionDescription sdp) {
|
||||
future.set(sdp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateFailure(String error) {
|
||||
future.setException(new PeerConnectionException(error));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetSuccess() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetFailure(String error) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}, mediaConstraints);
|
||||
|
||||
try {
|
||||
return correctSessionDescription(future.get());
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new PeerConnectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRemoteDescription(SessionDescription sdp) throws PeerConnectionException {
|
||||
final SettableFuture<Boolean> future = new SettableFuture<>();
|
||||
|
||||
peerConnection.setRemoteDescription(new SdpObserver() {
|
||||
@Override
|
||||
public void onCreateSuccess(SessionDescription sdp) {}
|
||||
|
||||
@Override
|
||||
public void onCreateFailure(String error) {}
|
||||
|
||||
@Override
|
||||
public void onSetSuccess() {
|
||||
future.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetFailure(String error) {
|
||||
future.setException(new PeerConnectionException(error));
|
||||
}
|
||||
}, sdp);
|
||||
|
||||
try {
|
||||
future.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new PeerConnectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLocalDescription(SessionDescription sdp) throws PeerConnectionException {
|
||||
final SettableFuture<Boolean> future = new SettableFuture<>();
|
||||
|
||||
peerConnection.setLocalDescription(new SdpObserver() {
|
||||
@Override
|
||||
public void onCreateSuccess(SessionDescription sdp) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateFailure(String error) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetSuccess() {
|
||||
future.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetFailure(String error) {
|
||||
future.setException(new PeerConnectionException(error));
|
||||
}
|
||||
}, sdp);
|
||||
|
||||
try {
|
||||
future.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new PeerConnectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
this.camera.dispose();
|
||||
|
||||
if (this.videoSource != null) {
|
||||
this.videoSource.dispose();
|
||||
}
|
||||
|
||||
this.audioSource.dispose();
|
||||
this.peerConnection.close();
|
||||
this.peerConnection.dispose();
|
||||
}
|
||||
|
||||
public boolean addIceCandidate(IceCandidate candidate) {
|
||||
return this.peerConnection.addIceCandidate(candidate);
|
||||
}
|
||||
|
||||
|
||||
private SessionDescription correctSessionDescription(SessionDescription sessionDescription) {
|
||||
String updatedSdp = sessionDescription.description.replaceAll("(a=fmtp:111 ((?!cbr=).)*)\r?\n", "$1;cbr=1\r\n");
|
||||
updatedSdp = updatedSdp.replaceAll(".+urn:ietf:params:rtp-hdrext:ssrc-audio-level.*\r?\n", "");
|
||||
|
||||
return new SessionDescription(sessionDescription.type, updatedSdp);
|
||||
}
|
||||
|
||||
public static class PeerConnectionException extends Exception {
|
||||
public PeerConnectionException(String error) {
|
||||
super(error);
|
||||
}
|
||||
|
||||
public PeerConnectionException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Camera implements CameraVideoCapturer.CameraSwitchHandler {
|
||||
|
||||
@Nullable
|
||||
private final CameraVideoCapturer capturer;
|
||||
private final CameraEventListener cameraEventListener;
|
||||
private final int cameraCount;
|
||||
|
||||
private CameraState.Direction activeDirection;
|
||||
private boolean enabled;
|
||||
|
||||
Camera(@NonNull Context context, @NonNull CameraEventListener cameraEventListener)
|
||||
{
|
||||
this.cameraEventListener = cameraEventListener;
|
||||
CameraEnumerator enumerator = getCameraEnumerator(context);
|
||||
cameraCount = enumerator.getDeviceNames().length;
|
||||
|
||||
CameraVideoCapturer capturerCandidate = createVideoCapturer(enumerator, FRONT);
|
||||
if (capturerCandidate != null) {
|
||||
activeDirection = FRONT;
|
||||
} else {
|
||||
capturerCandidate = createVideoCapturer(enumerator, BACK);
|
||||
if (capturerCandidate != null) {
|
||||
activeDirection = BACK;
|
||||
} else {
|
||||
activeDirection = NONE;
|
||||
}
|
||||
}
|
||||
capturer = capturerCandidate;
|
||||
}
|
||||
|
||||
void flip() {
|
||||
if (capturer == null || cameraCount < 2) {
|
||||
Log.w(TAG, "Tried to flip the camera, but we only have " + cameraCount + " of them.");
|
||||
return;
|
||||
}
|
||||
activeDirection = PENDING;
|
||||
capturer.switchCamera(this);
|
||||
}
|
||||
|
||||
void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
|
||||
if (capturer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (enabled) {
|
||||
capturer.startCapture(1280, 720, 30);
|
||||
} else {
|
||||
capturer.stopCapture();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Got interrupted while trying to stop video capture", e);
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
if (capturer != null) {
|
||||
capturer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return cameraCount;
|
||||
}
|
||||
|
||||
@NonNull CameraState.Direction getActiveDirection() {
|
||||
return enabled ? activeDirection : NONE;
|
||||
}
|
||||
|
||||
@Nullable CameraVideoCapturer getCapturer() {
|
||||
return capturer;
|
||||
}
|
||||
|
||||
private @Nullable CameraVideoCapturer createVideoCapturer(@NonNull CameraEnumerator enumerator,
|
||||
@NonNull CameraState.Direction direction)
|
||||
{
|
||||
String[] deviceNames = enumerator.getDeviceNames();
|
||||
for (String deviceName : deviceNames) {
|
||||
if ((direction == FRONT && enumerator.isFrontFacing(deviceName)) ||
|
||||
(direction == BACK && enumerator.isBackFacing(deviceName)))
|
||||
{
|
||||
return enumerator.createCapturer(deviceName, null);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private @NonNull CameraEnumerator getCameraEnumerator(@NonNull Context context) {
|
||||
boolean camera2EnumeratorIsSupported = false;
|
||||
try {
|
||||
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context);
|
||||
} catch (final Throwable throwable) {
|
||||
Log.w(TAG, "Camera2Enumator.isSupport() threw.", throwable);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Camera2 enumerator supported: " + camera2EnumeratorIsSupported);
|
||||
|
||||
return camera2EnumeratorIsSupported ? new Camera2Enumerator(context)
|
||||
: new Camera1Enumerator(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraSwitchDone(boolean isFrontFacing) {
|
||||
activeDirection = isFrontFacing ? FRONT : BACK;
|
||||
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraSwitchError(String errorMessage) {
|
||||
Log.e(TAG, "onCameraSwitchError: " + errorMessage);
|
||||
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
|
||||
}
|
||||
}
|
||||
|
||||
public interface CameraEventListener {
|
||||
void onCameraSwitchCompleted(@NonNull CameraState newCameraState);
|
||||
}
|
||||
}
|
2248
src/org/thoughtcrime/securesms/webrtc/WebRtcDataProtos.java
Normal file
2248
src/org/thoughtcrime/securesms/webrtc/WebRtcDataProtos.java
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue