diff --git a/res/drawable/webrtc_camera_flip_button.xml b/res/drawable/webrtc_camera_flip_button.xml
new file mode 100644
index 0000000000..e422c8295a
--- /dev/null
+++ b/res/drawable/webrtc_camera_flip_button.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/webrtc_call_controls.xml b/res/layout/webrtc_call_controls.xml
index 9e513c370b..fa0e5a06fb 100644
--- a/res/layout/webrtc_call_controls.xml
+++ b/res/layout/webrtc_call_controls.xml
@@ -34,6 +34,13 @@
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1c62de864e..ab10363c59 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -995,6 +995,7 @@
Signal Call
Mute
+ Use rear camera
Signal Call
diff --git a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java
index 39f0833d97..9c9b0989ed 100644
--- a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java
+++ b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java
@@ -139,6 +139,7 @@ public class WebRtcCallActivity extends Activity {
callScreen.setIncomingCallActionListener(new IncomingCallActionListener());
callScreen.setAudioMuteButtonListener(new AudioMuteButtonListener());
callScreen.setVideoMuteButtonListener(new VideoMuteButtonListener());
+ callScreen.setCameraFlipButtonListener(new CameraFlipButtonListener());
callScreen.setSpeakerButtonListener(new SpeakerButtonListener());
callScreen.setBluetoothButtonListener(new BluetoothButtonListener());
@@ -159,6 +160,13 @@ public class WebRtcCallActivity extends Activity {
startService(intent);
}
+ private void handleSetCameraFlip(boolean isRear) {
+ Intent intent = new Intent(this, WebRtcCallService.class);
+ intent.setAction(WebRtcCallService.ACTION_SET_CAMERA_FLIP);
+ intent.putExtra(WebRtcCallService.EXTRA_CAMERA_FLIP_REAR, isRear);
+ startService(intent);
+ }
+
private void handleAnswerCall() {
WebRtcViewModel event = EventBus.getDefault().getStickyEvent(WebRtcViewModel.class);
@@ -348,6 +356,11 @@ public class WebRtcCallActivity extends Activity {
}
}
+ private class CameraFlipButtonListener implements WebRtcCallControls.CameraFlipButtonListener {
+ @Override
+ public void onToggle(boolean isRear) { WebRtcCallActivity.this.handleSetCameraFlip(isRear); }
+ }
+
private class SpeakerButtonListener implements WebRtcCallControls.SpeakerButtonListener {
@Override
public void onSpeakerChange(boolean isSpeaker) {
diff --git a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallControls.java b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallControls.java
index 6c5d07c8b7..a7d8005279 100644
--- a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallControls.java
+++ b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallControls.java
@@ -27,6 +27,7 @@ public class WebRtcCallControls extends LinearLayout {
private AccessibleToggleButton audioMuteButton;
private AccessibleToggleButton videoMuteButton;
+ private AccessibleToggleButton cameraFlipButton;
private AccessibleToggleButton speakerButton;
private AccessibleToggleButton bluetoothButton;
@@ -60,6 +61,8 @@ public class WebRtcCallControls extends LinearLayout {
this.bluetoothButton = ViewUtil.findById(this, R.id.bluetoothButton);
this.audioMuteButton = ViewUtil.findById(this, R.id.muteButton);
this.videoMuteButton = ViewUtil.findById(this, R.id.video_mute_button);
+ this.cameraFlipButton = ViewUtil.findById(this, R.id.camera_flip_button);
+ this.cameraFlipButton.setVisibility(View.INVISIBLE); // shown once video is enabled
}
public void setAudioMuteButtonListener(final MuteButtonListener listener) {
@@ -75,7 +78,18 @@ public class WebRtcCallControls extends LinearLayout {
videoMuteButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- listener.onToggle(!isChecked);
+ boolean videoMuted = !isChecked;
+ listener.onToggle(videoMuted);
+ cameraFlipButton.setVisibility(videoMuted ? View.INVISIBLE : View.VISIBLE);
+ }
+ });
+ }
+
+ public void setCameraFlipButtonListener(final CameraFlipButtonListener listener) {
+ cameraFlipButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ listener.onToggle(isChecked);
}
});
}
@@ -138,21 +152,25 @@ public class WebRtcCallControls extends LinearLayout {
speakerButton.setAlpha(1.0f);
bluetoothButton.setAlpha(1.0f);
videoMuteButton.setAlpha(1.0f);
+ cameraFlipButton.setAlpha(1.0f);
audioMuteButton.setAlpha(1.0f);
speakerButton.setEnabled(true);
bluetoothButton.setEnabled(true);
videoMuteButton.setEnabled(true);
+ cameraFlipButton.setEnabled(true);
audioMuteButton.setEnabled(true);
} else if (!enabled && Build.VERSION.SDK_INT >= 11) {
speakerButton.setAlpha(0.3f);
bluetoothButton.setAlpha(0.3f);
videoMuteButton.setAlpha(0.3f);
+ cameraFlipButton.setAlpha(0.3f);
audioMuteButton.setAlpha(0.3f);
speakerButton.setEnabled(false);
bluetoothButton.setEnabled(false);
videoMuteButton.setEnabled(false);
+ cameraFlipButton.setEnabled(false);
audioMuteButton.setEnabled(false);
}
}
@@ -179,6 +197,10 @@ public class WebRtcCallControls extends LinearLayout {
public void onToggle(boolean isMuted);
}
+ public static interface CameraFlipButtonListener {
+ public void onToggle(boolean isRear);
+ }
+
public static interface SpeakerButtonListener {
public void onSpeakerChange(boolean isSpeaker);
}
diff --git a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java
index 7aa0500cc8..30d1765630 100644
--- a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java
+++ b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java
@@ -154,6 +154,10 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi
this.controls.setVideoMuteButtonListener(listener);
}
+ public void setCameraFlipButtonListener(WebRtcCallControls.CameraFlipButtonListener listener) {
+ this.controls.setCameraFlipButtonListener(listener);
+ }
+
public void setSpeakerButtonListener(WebRtcCallControls.SpeakerButtonListener listener) {
this.controls.setSpeakerButtonListener(listener);
}
diff --git a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java
index 2734cb586d..58aba5f928 100644
--- a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java
+++ b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java
@@ -116,6 +116,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
public static final String EXTRA_REMOTE_ADDRESS = "remote_address";
public static final String EXTRA_MUTE = "mute_value";
+ public static final String EXTRA_CAMERA_FLIP_REAR = "camera_flip_rear_value";
public static final String EXTRA_AVAILABLE = "enabled_value";
public static final String EXTRA_REMOTE_DESCRIPTION = "remote_description";
public static final String EXTRA_TIMESTAMP = "timestamp";
@@ -132,6 +133,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
public static final String ACTION_LOCAL_HANGUP = "LOCAL_HANGUP";
public static final String ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO";
public static final String ACTION_SET_MUTE_VIDEO = "SET_MUTE_VIDEO";
+ public static final String ACTION_SET_CAMERA_FLIP = "SET_CAMERA_FLIP";
public static final String ACTION_BLUETOOTH_CHANGE = "BLUETOOTH_CHANGE";
public static final String ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE";
public static final String ACTION_SCREEN_OFF = "SCREEN_OFF";
@@ -208,6 +210,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
else if (intent.getAction().equals(ACTION_REMOTE_HANGUP)) handleRemoteHangup(intent);
else if (intent.getAction().equals(ACTION_SET_MUTE_AUDIO)) handleSetMuteAudio(intent);
else if (intent.getAction().equals(ACTION_SET_MUTE_VIDEO)) handleSetMuteVideo(intent);
+ else if (intent.getAction().equals(ACTION_SET_CAMERA_FLIP)) handleSetCameraFlip(intent);
else if (intent.getAction().equals(ACTION_BLUETOOTH_CHANGE)) handleBluetoothChange(intent);
else if (intent.getAction().equals(ACTION_WIRED_HEADSET_CHANGE)) handleWiredHeadsetChange(intent);
else if (intent.getAction().equals((ACTION_SCREEN_OFF))) handleScreenOffChange(intent);
@@ -804,6 +807,17 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
sendMessage(viewModelStateFor(callState), this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
+ private void handleSetCameraFlip(Intent intent) {
+ boolean isRear = intent.getBooleanExtra(EXTRA_CAMERA_FLIP_REAR, false);
+ Log.w(TAG, "handleSetCameraFlip(isRear=" + isRear + ")...");
+
+ if (this.localVideoEnabled) {
+ if (this.peerConnection != null) {
+ this.peerConnection.flipCameras(isRear);
+ }
+ }
+ }
+
private void handleBluetoothChange(Intent intent) {
this.bluetoothAvailable = intent.getBooleanExtra(EXTRA_AVAILABLE, false);
diff --git a/src/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.java b/src/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.java
index 1b65b531d0..0e63b28c1d 100644
--- a/src/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.java
+++ b/src/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.java
@@ -40,8 +40,16 @@ public class PeerConnectionWrapper {
@NonNull private final AudioSource audioSource;
@Nullable private final VideoCapturer videoCapturer;
+ @Nullable private final VideoCapturer videoCapturerRear;
@Nullable private final VideoSource videoSource;
+ @Nullable private final VideoSource videoSourceRear;
@Nullable private final VideoTrack videoTrack;
+ @Nullable private final VideoTrack videoTrackRear;
+
+ @Nullable private VideoCapturer videoCapturerActive;
+ @Nullable private VideoTrack videoTrackActive;
+
+ @Nullable private final MediaStream mediaStream;
public PeerConnectionWrapper(@NonNull Context context,
@NonNull PeerConnectionFactory factory,
@@ -72,44 +80,71 @@ public class PeerConnectionWrapper {
this.peerConnection.setAudioPlayout(false);
this.peerConnection.setAudioRecording(false);
- this.videoCapturer = createVideoCapturer(context);
+ this.videoCapturer = createVideoCapturer(context, false);
+ this.videoCapturerRear = createVideoCapturer(context, true);
- MediaStream mediaStream = factory.createLocalMediaStream("ARDAMS");
+ this.videoCapturerActive = videoCapturer;
+
+ this.mediaStream = factory.createLocalMediaStream("ARDAMS");
this.audioSource = factory.createAudioSource(audioConstraints);
this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
this.audioTrack.setEnabled(false);
- mediaStream.addTrack(audioTrack);
+ this.mediaStream.addTrack(audioTrack);
if (videoCapturer != null) {
this.videoSource = factory.createVideoSource(videoCapturer);
this.videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
+ this.videoTrackActive = videoTrack;
+
this.videoTrack.addRenderer(new VideoRenderer(localRenderer));
this.videoTrack.setEnabled(false);
- mediaStream.addTrack(videoTrack);
+ this.mediaStream.addTrack(videoTrack);
} else {
this.videoSource = null;
this.videoTrack = null;
+ this.videoTrackActive = null;
+ }
+
+ if (videoCapturerRear != null) {
+ this.videoSourceRear = factory.createVideoSource(videoCapturerRear);
+ this.videoTrackRear = factory.createVideoTrack("ARDAMSv0", videoSourceRear);
+ this.videoTrackRear.addRenderer(new VideoRenderer(localRenderer));
+ this.videoTrackRear.setEnabled(false);
+ } else {
+ this.videoSourceRear = null;
+ this.videoTrackRear = null;
}
this.peerConnection.addStream(mediaStream);
}
public void setVideoEnabled(boolean enabled) {
- if (this.videoTrack != null) {
- this.videoTrack.setEnabled(enabled);
+ if (this.videoTrackActive != null) {
+ this.videoTrackActive.setEnabled(enabled);
}
- if (this.videoCapturer != null) {
+ if (this.videoCapturerActive != null) {
try {
- if (enabled) this.videoCapturer.startCapture(1280, 720, 30);
- else this.videoCapturer.stopCapture();
+ if (enabled) this.videoCapturerActive.startCapture(1280, 720, 30);
+ else this.videoCapturerActive.stopCapture();
} catch (InterruptedException e) {
Log.w(TAG, e);
}
}
}
+ public void flipCameras(boolean isRear) {
+ if (videoCapturerRear != null) {
+ setVideoEnabled(false);
+ mediaStream.removeTrack(videoTrackActive);
+ this.videoTrackActive = isRear ? videoTrackRear : videoTrack;
+ this.videoCapturerActive = isRear ? videoCapturerRear : videoCapturer;
+ mediaStream.addTrack(videoTrackActive);
+ setVideoEnabled(true);
+ }
+ }
+
public void setCommunicationMode() {
this.peerConnection.setAudioPlayout(true);
this.peerConnection.setAudioRecording(true);
@@ -268,10 +303,23 @@ public class PeerConnectionWrapper {
this.videoCapturer.dispose();
}
+ if (this.videoCapturerRear != null) {
+ try {
+ this.videoCapturerRear.stopCapture();
+ } catch (InterruptedException e) {
+ Log.w(TAG, e);
+ }
+ this.videoCapturerRear.dispose();
+ }
+
if (this.videoSource != null) {
this.videoSource.dispose();
}
+ if (this.videoSourceRear != null) {
+ this.videoSourceRear.dispose();
+ }
+
this.audioSource.dispose();
this.peerConnection.close();
this.peerConnection.dispose();
@@ -281,7 +329,7 @@ public class PeerConnectionWrapper {
return this.peerConnection.addIceCandidate(candidate);
}
- private @Nullable CameraVideoCapturer createVideoCapturer(@NonNull Context context) {
+ private @Nullable CameraVideoCapturer createVideoCapturer(@NonNull Context context, boolean rear) {
boolean camera2EnumeratorIsSupported = false;
try {
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context);
@@ -298,12 +346,16 @@ public class PeerConnectionWrapper {
String[] deviceNames = enumerator.getDeviceNames();
for (String deviceName : deviceNames) {
- if (enumerator.isFrontFacing(deviceName)) {
- Log.w(TAG, "Creating front facing camera capturer.");
+ boolean isDesiredDirection =
+ rear ? enumerator.isBackFacing(deviceName)
+ : enumerator.isFrontFacing(deviceName);
+ if (isDesiredDirection) {
+ String direction = rear ? "rear" : "front";
+ Log.w(TAG, "Creating " + direction + " facing camera capturer.");
final CameraVideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
- Log.w(TAG, "Found front facing capturer: " + deviceName);
+ Log.w(TAG, "Found " + direction + " facing capturer: " + deviceName);
return videoCapturer;
}
@@ -311,7 +363,10 @@ public class PeerConnectionWrapper {
}
for (String deviceName : deviceNames) {
- if (!enumerator.isFrontFacing(deviceName)) {
+ boolean isDesiredDirection =
+ rear ? enumerator.isBackFacing(deviceName)
+ : enumerator.isFrontFacing(deviceName);
+ if (!isDesiredDirection) {
Log.w(TAG, "Creating other camera capturer.");
final CameraVideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);