Add ringrtc support.

RingRTC provides Signal Messenger applications with a common interface
for video and voice calling services built on top of WebRTC.
master
Curt Brune 2019-10-15 10:29:05 -07:00 committed by Greyson Parrelli
parent adc0907906
commit 03cbee0277
14 changed files with 766 additions and 3175 deletions

View File

@ -88,7 +88,7 @@ dependencies {
implementation 'org.whispersystems:signal-service-android:2.13.8'
implementation 'org.whispersystems:webrtc-android:M77'
implementation 'org.signal:ringrtc-android:0.1.4'
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:893d42dfa6d08fc1268c4da5aee6f6f3f1bb683ad4a241ef2f3e3d4dfb114e47',
'org.whispersystems:webrtc-android:c1254d6d8871c8a5a0e02f682626d0fc0ba1633587334c43edc75119f6977ac2',
'org.signal:ringrtc-android:8d336bde27de9a23e483843461c0423c147a191c3af97f933b8a85d406560957',
'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774',
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',

View File

@ -1,3 +0,0 @@
-dontwarn org.webrtc.NetworkMonitorAutoDetect
-dontwarn android.net.Network
-keep class org.webrtc.** { *; }

View File

@ -1,3 +1,3 @@
all:
protoc --java_out=../src/ WebRtcData.proto Backups.proto DeviceName.proto
protoc --java_out=../src/ Backups.proto DeviceName.proto

View File

@ -1,31 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package signal;
option java_package = "org.thoughtcrime.securesms.webrtc";
option java_outer_classname = "WebRtcDataProtos";
message Connected {
optional uint64 id = 1;
}
message Hangup {
optional uint64 id = 1;
}
message VideoStreamingStatus {
optional uint64 id = 1;
optional bool enabled = 2;
}
message Data {
optional Connected connected = 1;
optional Hangup hangup = 2;
optional VideoStreamingStatus videoStreamingStatus = 3;
}

View File

@ -34,6 +34,7 @@ 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;
@ -73,8 +74,6 @@ import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.VersionTracker;
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;
@ -130,7 +129,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
initializeSignedPreKeyCheck();
initializePeriodicTasks();
initializeCircumvention();
initializeWebRtc();
initializeRingRtc();
initializePendingMessages();
initializeUnidentifiedDeliveryAbilityRefresh();
initializeBlobProvider();
@ -305,7 +304,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
}
}
private void initializeWebRtc() {
private void initializeRingRtc() {
try {
Set<String> HARDWARE_AEC_BLACKLIST = new HashSet<String>() {{
add("Pixel");
@ -334,7 +333,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
}
PeerConnectionFactory.initialize(InitializationOptions.builder(this).createInitializationOptions());
CallConnectionFactory.initialize(this);
} catch (UnsatisfiedLinkError e) {
Log.w(TAG, e);
}

View File

@ -44,9 +44,9 @@ import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
import org.thoughtcrime.securesms.ringrtc.CameraState;
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;

View File

@ -4,7 +4,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.webrtc.CameraState;
import org.thoughtcrime.securesms.ringrtc.CameraState;
import org.webrtc.SurfaceViewRenderer;
import org.whispersystems.libsignal.IdentityKey;

View File

@ -0,0 +1,288 @@
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 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);
}
}

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.webrtc;
package org.thoughtcrime.securesms.ringrtc;
import androidx.annotation.NonNull;

View File

@ -0,0 +1,106 @@
package org.thoughtcrime.securesms.ringrtc;
import android.content.Context;
import androidx.annotation.NonNull;
import java.io.IOException;
import java.util.List;
import org.signal.ringrtc.SignalMessageRecipient;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
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(@NonNull SignalServiceMessageSender messageSender,
@NonNull Recipient recipient)
{
this.recipient = recipient;
this.messageSender = messageSender;
}
public @NonNull RecipientId getId() {
return recipient.getId();
}
@Override
public boolean isEqual(@NonNull SignalMessageRecipient inRecipient) {
if (!(inRecipient instanceof MessageRecipient)) {
return false;
}
if (getClass() != inRecipient.getClass()) {
Log.e(TAG, "CLASSES NOT EQUAL: " + getClass().toString() + ", " + recipient.getClass().toString());
return false;
}
MessageRecipient that = (MessageRecipient) inRecipient;
return recipient.equals(that.recipient);
}
private void sendMessage(Context context, SignalServiceCallMessage callMessage)
throws UntrustedIdentityException, IOException
{
messageSender.sendCallMessage(new SignalServiceAddress(recipient.requireAddress().toPhoneString()),
UnidentifiedAccessUtil.getAccessFor(context, recipient),
callMessage);
}
@Override
public void sendOfferMessage(Context context, long callId, String description)
throws UntrustedIdentityException, IOException
{
Log.i(TAG, "MessageRecipient::sendOfferMessage(): callId: 0x" + Long.toHexString(callId));
OfferMessage offerMessage = new OfferMessage(callId, description);
sendMessage(context, SignalServiceCallMessage.forOffer(offerMessage));
}
@Override
public void sendAnswerMessage(Context context, long callId, String description)
throws UntrustedIdentityException, IOException
{
Log.i(TAG, "MessageRecipient::sendAnswerMessage(): callId: 0x" + Long.toHexString(callId));
AnswerMessage answerMessage = new AnswerMessage(callId, description);
sendMessage(context, SignalServiceCallMessage.forAnswer(answerMessage));
}
@Override
public void sendIceUpdates(Context context, List<IceUpdateMessage> iceUpdateMessages)
throws UntrustedIdentityException, IOException
{
Log.i(TAG, "MessageRecipient::sendIceUpdates(): iceUpdates: " + iceUpdateMessages.size());
sendMessage(context, SignalServiceCallMessage.forIceUpdates(iceUpdateMessages));
}
@Override
public void sendHangupMessage(Context context, long callId)
throws UntrustedIdentityException, IOException
{
Log.i(TAG, "MessageRecipient::sendHangupMessage(): callId: 0x" + Long.toHexString(callId));
HangupMessage hangupMessage = new HangupMessage(callId);
sendMessage(context, SignalServiceCallMessage.forHangup(hangupMessage));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +0,0 @@
package org.thoughtcrime.securesms.webrtc;
import org.webrtc.PeerConnectionFactory;
public class PeerConnectionFactoryOptions extends PeerConnectionFactory.Options {
public PeerConnectionFactoryOptions() {
this.networkIgnoreMask = 1 << 4;
}
}

View File

@ -1,424 +0,0 @@
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);
}
}

File diff suppressed because it is too large Load Diff