From ca8fecea9cdf338163e61745f3d92ece7f91cdd1 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 25 Apr 2018 11:00:03 -0700 Subject: [PATCH] Clean up camera flipping, handle having missing cameras. Did a refactor to better organize the camera flipping code. Also, I wanted to make sure we handle the cases where the user doesn't have two cameras (or no cameras, for that matter). In these cases, we just don't show the appropriate buttons. --- res/drawable/webrtc_camera_front_button.xml | 8 + ...tton.xml => webrtc_camera_rear_button.xml} | 1 - res/layout/webrtc_call_controls.xml | 3 +- res/values/strings.xml | 2 +- .../securesms/WebRtcCallActivity.java | 12 +- .../components/webrtc/WebRtcCallControls.java | 77 ++--- .../components/webrtc/WebRtcCallScreen.java | 25 +- .../securesms/events/WebRtcViewModel.java | 48 +-- .../securesms/service/WebRtcCallService.java | 141 +++++---- .../securesms/webrtc/CameraState.java | 32 ++ .../webrtc/PeerConnectionWrapper.java | 281 ++++++++++-------- 11 files changed, 372 insertions(+), 258 deletions(-) create mode 100644 res/drawable/webrtc_camera_front_button.xml rename res/drawable/{webrtc_camera_flip_button.xml => webrtc_camera_rear_button.xml} (78%) create mode 100644 src/org/thoughtcrime/securesms/webrtc/CameraState.java diff --git a/res/drawable/webrtc_camera_front_button.xml b/res/drawable/webrtc_camera_front_button.xml new file mode 100644 index 000000000..cda55648d --- /dev/null +++ b/res/drawable/webrtc_camera_front_button.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/res/drawable/webrtc_camera_flip_button.xml b/res/drawable/webrtc_camera_rear_button.xml similarity index 78% rename from res/drawable/webrtc_camera_flip_button.xml rename to res/drawable/webrtc_camera_rear_button.xml index e422c8295..697a1b4e5 100644 --- a/res/drawable/webrtc_camera_flip_button.xml +++ b/res/drawable/webrtc_camera_rear_button.xml @@ -1,6 +1,5 @@ - + android:background="@drawable/webrtc_camera_rear_button" + android:visibility="gone"/> diff --git a/res/values/strings.xml b/res/values/strings.xml index ab10363c5..c298f1454 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -995,7 +995,7 @@ Signal Call Mute - Use rear camera + Switch Cameras Signal Call diff --git a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java index 9c9b0989e..096b0ff4c 100644 --- a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ViewUtil; +import org.thoughtcrime.securesms.webrtc.CameraState; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.SignalProtocolAddress; @@ -160,10 +161,9 @@ public class WebRtcCallActivity extends Activity { startService(intent); } - private void handleSetCameraFlip(boolean isRear) { + private void handleFlipCamera() { Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_SET_CAMERA_FLIP); - intent.putExtra(WebRtcCallService.EXTRA_CAMERA_FLIP_REAR, isRear); + intent.setAction(WebRtcCallService.ACTION_FLIP_CAMERA); startService(intent); } @@ -330,10 +330,10 @@ public class WebRtcCallActivity extends Activity { case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break; } - callScreen.setLocalVideoEnabled(event.isLocalVideoEnabled()); callScreen.setRemoteVideoEnabled(event.isRemoteVideoEnabled()); callScreen.updateAudioState(event.isBluetoothAvailable(), event.isMicrophoneEnabled()); callScreen.setControlsEnabled(event.getState() != WebRtcViewModel.State.CALL_INCOMING); + callScreen.setLocalVideoState(event.getLocalCameraState()); } private class HangupButtonListener implements WebRtcCallScreen.HangupButtonListener { @@ -358,7 +358,9 @@ public class WebRtcCallActivity extends Activity { private class CameraFlipButtonListener implements WebRtcCallControls.CameraFlipButtonListener { @Override - public void onToggle(boolean isRear) { WebRtcCallActivity.this.handleSetCameraFlip(isRear); } + public void onToggle() { + WebRtcCallActivity.this.handleFlipCamera(); + } } private class SpeakerButtonListener implements WebRtcCallControls.SpeakerButtonListener { diff --git a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallControls.java b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallControls.java index a7d800527..7b091662b 100644 --- a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallControls.java +++ b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallControls.java @@ -5,8 +5,8 @@ import android.annotation.TargetApi; import android.content.Context; import android.media.AudioManager; import android.os.Build; +import android.support.annotation.NonNull; import android.util.AttributeSet; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.AccessibleToggleButton; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.ViewUtil; +import org.thoughtcrime.securesms.webrtc.CameraState; public class WebRtcCallControls extends LinearLayout { @@ -27,9 +28,10 @@ public class WebRtcCallControls extends LinearLayout { private AccessibleToggleButton audioMuteButton; private AccessibleToggleButton videoMuteButton; - private AccessibleToggleButton cameraFlipButton; private AccessibleToggleButton speakerButton; private AccessibleToggleButton bluetoothButton; + private AccessibleToggleButton cameraFlipButton; + private boolean cameraFlipAvailable; @TargetApi(Build.VERSION_CODES.LOLLIPOP) public WebRtcCallControls(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { @@ -62,7 +64,6 @@ public class WebRtcCallControls extends LinearLayout { 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) { @@ -80,7 +81,7 @@ public class WebRtcCallControls extends LinearLayout { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { boolean videoMuted = !isChecked; listener.onToggle(videoMuted); - cameraFlipButton.setVisibility(videoMuted ? View.INVISIBLE : View.VISIBLE); + cameraFlipButton.setVisibility(!videoMuted && cameraFlipAvailable ? View.VISIBLE : View.GONE); } }); } @@ -89,7 +90,10 @@ public class WebRtcCallControls extends LinearLayout { cameraFlipButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - listener.onToggle(isChecked); + listener.onToggle(); + cameraFlipButton.setBackgroundResource(isChecked ? R.drawable.webrtc_camera_front_button + : R.drawable.webrtc_camera_rear_button); + cameraFlipButton.setEnabled(false); } }); } @@ -143,40 +147,46 @@ public class WebRtcCallControls extends LinearLayout { videoMuteButton.setChecked(enabled, false); } + public void setVideoAvailable(boolean available) { + videoMuteButton.setVisibility(available ? VISIBLE : GONE); + } + + public void setCameraFlipButtonEnabled(boolean enabled) { + cameraFlipButton.setChecked(enabled, false); + } + + public void setCameraFlipAvailable(boolean available) { + cameraFlipAvailable = available; + } + + public void setCameraFlipClickable(boolean clickable) { + setControlEnabled(cameraFlipButton, clickable); + } + public void setMicrophoneEnabled(boolean enabled) { audioMuteButton.setChecked(!enabled, false); } public void setControlsEnabled(boolean enabled) { - if (enabled && Build.VERSION.SDK_INT >= 11) { - speakerButton.setAlpha(1.0f); - bluetoothButton.setAlpha(1.0f); - videoMuteButton.setAlpha(1.0f); - cameraFlipButton.setAlpha(1.0f); - audioMuteButton.setAlpha(1.0f); + setControlEnabled(speakerButton, enabled); + setControlEnabled(bluetoothButton, enabled); + setControlEnabled(videoMuteButton, enabled); + setControlEnabled(cameraFlipButton, enabled); + setControlEnabled(audioMuteButton, enabled); + } - 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); + private void setControlEnabled(@NonNull View view, boolean enabled) { + if (enabled) { + view.setAlpha(1.0f); + view.setEnabled(true); + } else { + view.setAlpha(0.3f); + view.setEnabled(false); } } public void displayVideoTooltip(ViewGroup viewGroup) { - if (Build.VERSION.SDK_INT > 15) { + if (Build.VERSION.SDK_INT > 15 && videoMuteButton.getVisibility() == VISIBLE) { final ToolTipsManager toolTipsManager = new ToolTipsManager(); ToolTip toolTip = new ToolTip.Builder(getContext(), videoMuteButton, viewGroup, @@ -184,12 +194,7 @@ public class WebRtcCallControls extends LinearLayout { ToolTip.POSITION_BELOW).build(); toolTipsManager.show(toolTip); - videoMuteButton.postDelayed(new Runnable() { - @Override - public void run() { - toolTipsManager.findAndDismiss(videoMuteButton); - } - }, 4000); + videoMuteButton.postDelayed(() -> toolTipsManager.findAndDismiss(videoMuteButton), 4000); } } @@ -198,7 +203,7 @@ public class WebRtcCallControls extends LinearLayout { } public static interface CameraFlipButtonListener { - public void onToggle(boolean isRear); + public void onToggle(); } public static interface SpeakerButtonListener { diff --git a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java index 30d176563..7db63b4a8 100644 --- a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java +++ b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java @@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.service.WebRtcCallService; 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; @@ -62,6 +63,7 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi private static final String TAG = WebRtcCallScreen.class.getSimpleName(); private ImageView photo; + private SurfaceViewRenderer localRenderer; private PercentFrameLayout localRenderLayout; private PercentFrameLayout remoteRenderLayout; private TextView name; @@ -187,14 +189,19 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi this.controls.setControlsEnabled(enabled); } - public void setLocalVideoEnabled(boolean enabled) { - if (enabled && this.localRenderLayout.isHidden()) { - this.controls.setVideoEnabled(true); - this.localRenderLayout.setHidden(false); - this.localRenderLayout.requestLayout(); - } else if (!enabled && !this.localRenderLayout.isHidden()){ - this.controls.setVideoEnabled(false); - this.localRenderLayout.setHidden(true); + public void setLocalVideoState(@NonNull CameraState cameraState) { + this.controls.setVideoAvailable(cameraState.getCameraCount() > 0); + this.controls.setVideoEnabled(cameraState.isEnabled()); + this.controls.setCameraFlipAvailable(cameraState.getCameraCount() > 1); + this.controls.setCameraFlipClickable(cameraState.getActiveDirection() != CameraState.Direction.PENDING); + this.controls.setCameraFlipButtonEnabled(cameraState.getActiveDirection() == CameraState.Direction.BACK); + + if (this.localRenderer != null) { + this.localRenderer.setMirror(cameraState.getActiveDirection() == CameraState.Direction.FRONT); + } + + if (this.localRenderLayout.isHidden() == cameraState.isEnabled()) { + this.localRenderLayout.setHidden(!cameraState.isEnabled()); this.localRenderLayout.requestLayout(); } } @@ -276,6 +283,8 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi localRenderLayout.addView(localRenderer); remoteRenderLayout.addView(remoteRenderer); + + this.localRenderer = localRenderer; } } diff --git a/src/org/thoughtcrime/securesms/events/WebRtcViewModel.java b/src/org/thoughtcrime/securesms/events/WebRtcViewModel.java index 325a6730e..aeeb78c74 100644 --- a/src/org/thoughtcrime/securesms/events/WebRtcViewModel.java +++ b/src/org/thoughtcrime/securesms/events/WebRtcViewModel.java @@ -4,6 +4,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.webrtc.CameraState; import org.whispersystems.libsignal.IdentityKey; public class WebRtcViewModel { @@ -30,29 +31,40 @@ public class WebRtcViewModel { private final @Nullable IdentityKey identityKey; private final boolean remoteVideoEnabled; - private final boolean localVideoEnabled; private final boolean isBluetoothAvailable; private final boolean isMicrophoneEnabled; - public WebRtcViewModel(@NonNull State state, @NonNull Recipient recipient, - boolean localVideoEnabled, boolean remoteVideoEnabled, - boolean isBluetoothAvailable, boolean isMicrophoneEnabled) + private final CameraState localCameraState; + + public WebRtcViewModel(@NonNull State state, + @NonNull Recipient recipient, + @NonNull CameraState localCameraState, + boolean remoteVideoEnabled, + boolean isBluetoothAvailable, + boolean isMicrophoneEnabled) { - this(state, recipient, null, - localVideoEnabled, remoteVideoEnabled, - isBluetoothAvailable, isMicrophoneEnabled); + this(state, + recipient, + null, + localCameraState, + remoteVideoEnabled, + isBluetoothAvailable, + isMicrophoneEnabled); } - public WebRtcViewModel(@NonNull State state, @NonNull Recipient recipient, + public WebRtcViewModel(@NonNull State state, + @NonNull Recipient recipient, @Nullable IdentityKey identityKey, - boolean localVideoEnabled, boolean remoteVideoEnabled, - boolean isBluetoothAvailable, boolean isMicrophoneEnabled) + @NonNull CameraState localCameraState, + boolean remoteVideoEnabled, + boolean isBluetoothAvailable, + boolean isMicrophoneEnabled) { this.state = state; this.recipient = recipient; + this.localCameraState = localCameraState; this.identityKey = identityKey; - this.localVideoEnabled = localVideoEnabled; this.remoteVideoEnabled = remoteVideoEnabled; this.isBluetoothAvailable = isBluetoothAvailable; this.isMicrophoneEnabled = isMicrophoneEnabled; @@ -66,8 +78,11 @@ public class WebRtcViewModel { return recipient; } - @Nullable - public IdentityKey getIdentityKey() { + public @NonNull CameraState getLocalCameraState() { + return localCameraState; + } + + public @Nullable IdentityKey getIdentityKey() { return identityKey; } @@ -75,10 +90,6 @@ public class WebRtcViewModel { return remoteVideoEnabled; } - public boolean isLocalVideoEnabled() { - return localVideoEnabled; - } - public boolean isBluetoothAvailable() { return isBluetoothAvailable; } @@ -87,7 +98,8 @@ public class WebRtcViewModel { return isMicrophoneEnabled; } + public String toString() { - return "[State: " + state + ", recipient: " + recipient.getAddress() + ", identity: " + identityKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localVideoEnabled + "]"; + return "[State: " + state + ", recipient: " + recipient.getAddress() + ", identity: " + identityKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localCameraState.isEnabled() + "]"; } } diff --git a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java index 58aba5f92..81c428412 100644 --- a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -30,7 +30,6 @@ import org.thoughtcrime.securesms.WebRtcCallActivity; import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.events.WebRtcViewModel; @@ -43,6 +42,7 @@ import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder; +import org.thoughtcrime.securesms.webrtc.CameraState; import org.thoughtcrime.securesms.webrtc.IncomingPstnCallReceiver; import org.thoughtcrime.securesms.webrtc.PeerConnectionFactoryOptions; import org.thoughtcrime.securesms.webrtc.PeerConnectionWrapper; @@ -104,7 +104,12 @@ import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INC import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_RINGING; import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_OUTGOING_RINGING; -public class WebRtcCallService extends Service implements InjectableType, PeerConnection.Observer, DataChannel.Observer, BluetoothStateManager.BluetoothStateListener { +public class WebRtcCallService extends Service implements InjectableType, + PeerConnection.Observer, + DataChannel.Observer, + BluetoothStateManager.BluetoothStateListener, + PeerConnectionWrapper.CameraEventListener +{ private static final String TAG = WebRtcCallService.class.getSimpleName(); @@ -116,7 +121,6 @@ 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"; @@ -133,7 +137,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_FLIP_CAMERA = "FLIP_CAMERA"; 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"; @@ -149,11 +153,11 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo public static final String ACTION_REMOTE_VIDEO_MUTE = "REMOTE_VIDEO_MUTE"; public static final String ACTION_ICE_CONNECTED = "ICE_CONNECTED"; - private CallState callState = CallState.STATE_IDLE; - private boolean microphoneEnabled = true; - private boolean localVideoEnabled = false; - private boolean remoteVideoEnabled = false; - private boolean bluetoothAvailable = false; + private CallState callState = CallState.STATE_IDLE; + private CameraState localCameraState = CameraState.UNKNOWN; + private boolean microphoneEnabled = true; + private boolean remoteVideoEnabled = false; + private boolean bluetoothAvailable = false; @Inject public SignalServiceMessageSender messageSender; @Inject public SignalServiceAccountManager accountManager; @@ -210,7 +214,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_FLIP_CAMERA)) 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); @@ -265,6 +269,15 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo startService(intent); } + @Override + public void onCameraSwitchCompleted(@NonNull CameraState newCameraState) { + this.localCameraState = newCameraState; + if (recipient != null) { + sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + } + } + + // Initializers private void initializeResources() { @@ -358,7 +371,8 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo boolean isAlwaysTurn = TextSecurePreferences.isTurnOnly(WebRtcCallService.this); - WebRtcCallService.this.peerConnection = new PeerConnectionWrapper(WebRtcCallService.this, peerConnectionFactory, WebRtcCallService.this, localRenderer, result, !isSystemContact || isAlwaysTurn); + WebRtcCallService.this.peerConnection = new PeerConnectionWrapper(WebRtcCallService.this, peerConnectionFactory, WebRtcCallService.this, localRenderer, result, WebRtcCallService.this, !isSystemContact || isAlwaysTurn); + WebRtcCallService.this.localCameraState = WebRtcCallService.this.peerConnection.getCameraState(); WebRtcCallService.this.peerConnection.setRemoteDescription(new SessionDescription(SessionDescription.Type.OFFER, offer)); WebRtcCallService.this.lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING); @@ -379,6 +393,10 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo terminate(); } }); + + if (recipient != null) { + sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + } } catch (PeerConnectionException e) { Log.w(TAG, e); terminate(); @@ -400,7 +418,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo initializeVideo(); - sendMessage(WebRtcViewModel.State.CALL_OUTGOING, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.CALL_OUTGOING, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL); audioManager.initializeAudioForCall(); audioManager.startOutgoingRinger(OutgoingRinger.Type.SONAR); @@ -417,7 +435,8 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo try { boolean isAlwaysTurn = TextSecurePreferences.isTurnOnly(WebRtcCallService.this); - WebRtcCallService.this.peerConnection = new PeerConnectionWrapper(WebRtcCallService.this, peerConnectionFactory, WebRtcCallService.this, localRenderer, result, isAlwaysTurn); + WebRtcCallService.this.peerConnection = new PeerConnectionWrapper(WebRtcCallService.this, peerConnectionFactory, WebRtcCallService.this, localRenderer, result, WebRtcCallService.this, isAlwaysTurn); + WebRtcCallService.this.localCameraState = WebRtcCallService.this.peerConnection.getCameraState(); WebRtcCallService.this.dataChannel = WebRtcCallService.this.peerConnection.createDataChannel(DATA_CHANNEL_NAME); WebRtcCallService.this.dataChannel.registerObserver(WebRtcCallService.this); @@ -434,16 +453,20 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo Log.w(TAG, error); if (error instanceof UntrustedIdentityException) { - sendMessage(WebRtcViewModel.State.UNTRUSTED_IDENTITY, recipient, ((UntrustedIdentityException)error).getIdentityKey(), localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.UNTRUSTED_IDENTITY, recipient, ((UntrustedIdentityException)error).getIdentityKey(), localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } else if (error instanceof UnregisteredUserException) { - sendMessage(WebRtcViewModel.State.NO_SUCH_USER, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.NO_SUCH_USER, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } else if (error instanceof IOException) { - sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } terminate(); } }); + + if (recipient != null) { + sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + } } catch (PeerConnectionException e) { Log.w(TAG, e); terminate(); @@ -475,7 +498,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo @Override public void onFailureContinue(Throwable error) { Log.w(TAG, error); - sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); terminate(); } @@ -529,7 +552,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo @Override public void onFailureContinue(Throwable error) { Log.w(TAG, error); - sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); terminate(); } @@ -543,7 +566,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo this.callState = CallState.STATE_LOCAL_RINGING; this.lockManager.updatePhoneState(LockManager.PhoneState.INTERACTIVE); - sendMessage(WebRtcViewModel.State.CALL_INCOMING, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.CALL_INCOMING, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); startCallCardActivity(); audioManager.initializeAudioForCall(); @@ -565,7 +588,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo this.callState = CallState.STATE_REMOTE_RINGING; this.audioManager.startOutgoingRinger(OutgoingRinger.Type.RINGING); - sendMessage(WebRtcViewModel.State.CALL_RINGING, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.CALL_RINGING, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } } @@ -589,10 +612,10 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo callState = CallState.STATE_CONNECTED; - if (localVideoEnabled) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO); - else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL); + if (localCameraState.isEnabled()) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO); + else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL); - sendMessage(WebRtcViewModel.State.CALL_CONNECTED, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.CALL_CONNECTED, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); unregisterPowerButtonReceiver(); @@ -600,12 +623,12 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo this.peerConnection.setCommunicationMode(); this.peerConnection.setAudioEnabled(microphoneEnabled); - this.peerConnection.setVideoEnabled(localVideoEnabled); + this.peerConnection.setVideoEnabled(localCameraState.isEnabled()); this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder() .setVideoStreamingStatus(WebRtcDataProtos.VideoStreamingStatus.newBuilder() .setId(this.callId) - .setEnabled(localVideoEnabled)) + .setEnabled(localCameraState.isEnabled())) .build().toByteArray()), false)); } @@ -644,7 +667,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo return; } - sendMessage(WebRtcViewModel.State.CALL_BUSY, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.CALL_BUSY, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); audioManager.startOutgoingRinger(OutgoingRinger.Type.BUSY); Util.runOnMainDelayed(new Runnable() { @@ -666,7 +689,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo this.callState != CallState.STATE_CONNECTED) { Log.w(TAG, "Timing out call: " + this.callId); - sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); if (this.callState == CallState.STATE_ANSWERING || this.callState == CallState.STATE_LOCAL_RINGING) { insertMissedCall(this.recipient, true); @@ -735,7 +758,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder().setHangup(Hangup.newBuilder().setId(this.callId)).build().toByteArray()), false)); sendMessage(this.recipient, SignalServiceCallMessage.forHangup(new HangupMessage(this.callId))); - sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } terminate(); @@ -752,9 +775,9 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo } if (this.callState == CallState.STATE_DIALING || this.callState == CallState.STATE_REMOTE_RINGING) { - sendMessage(WebRtcViewModel.State.RECIPIENT_UNAVAILABLE, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.RECIPIENT_UNAVAILABLE, this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } else { - sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } if (this.callState == CallState.STATE_ANSWERING || this.callState == CallState.STATE_LOCAL_RINGING) { @@ -777,26 +800,25 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo AudioManager audioManager = ServiceUtil.getAudioManager(this); boolean muted = intent.getBooleanExtra(EXTRA_MUTE, false); - this.localVideoEnabled = !muted; - if (this.peerConnection != null) { - this.peerConnection.setVideoEnabled(this.localVideoEnabled); + this.peerConnection.setVideoEnabled(!muted); + this.localCameraState = this.peerConnection.getCameraState(); } if (this.callId != null && this.dataChannel != null) { this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder() .setVideoStreamingStatus(WebRtcDataProtos.VideoStreamingStatus.newBuilder() .setId(this.callId) - .setEnabled(localVideoEnabled)) + .setEnabled(!muted)) .build().toByteArray()), false)); } if (callState == CallState.STATE_CONNECTED) { - if (localVideoEnabled) this.lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO); - else this.lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL); + if (localCameraState.isEnabled()) this.lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO); + else this.lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL); } - if (localVideoEnabled && + if (localCameraState.isEnabled() && !audioManager.isSpeakerphoneOn() && !audioManager.isBluetoothScoOn() && !audioManager.isWiredHeadsetOn()) @@ -804,16 +826,17 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo audioManager.setSpeakerphoneOn(true); } - sendMessage(viewModelStateFor(callState), this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(viewModelStateFor(callState), this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } private void handleSetCameraFlip(Intent intent) { - boolean isRear = intent.getBooleanExtra(EXTRA_CAMERA_FLIP_REAR, false); - Log.w(TAG, "handleSetCameraFlip(isRear=" + isRear + ")..."); + Log.w(TAG, "handleSetCameraFlip()..."); - if (this.localVideoEnabled) { - if (this.peerConnection != null) { - this.peerConnection.flipCameras(isRear); + if (localCameraState.isEnabled() && peerConnection != null) { + peerConnection.flipCamera(); + localCameraState = peerConnection.getCameraState(); + if (recipient != null) { + sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } } } @@ -822,7 +845,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo this.bluetoothAvailable = intent.getBooleanExtra(EXTRA_AVAILABLE, false); if (recipient != null) { - sendMessage(viewModelStateFor(callState), recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } } @@ -839,12 +862,12 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo if (present && audioManager.isSpeakerphoneOn()) { audioManager.setSpeakerphoneOn(false); audioManager.setBluetoothScoOn(false); - } else if (!present && !audioManager.isSpeakerphoneOn() && !audioManager.isBluetoothScoOn() && localVideoEnabled) { + } else if (!present && !audioManager.isSpeakerphoneOn() && !audioManager.isBluetoothScoOn() && localCameraState.isEnabled()) { audioManager.setSpeakerphoneOn(true); } if (recipient != null) { - sendMessage(viewModelStateFor(callState), recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } } } @@ -868,7 +891,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo } this.remoteVideoEnabled = !muted; - sendMessage(WebRtcViewModel.State.CALL_CONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); + sendMessage(WebRtcViewModel.State.CALL_CONNECTED, this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled); } /// Helper Methods @@ -932,10 +955,10 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo } this.callState = CallState.STATE_IDLE; + this.localCameraState = CameraState.UNKNOWN; this.recipient = null; this.callId = null; this.microphoneEnabled = true; - this.localVideoEnabled = false; this.remoteVideoEnabled = false; this.pendingOutgoingIceUpdates = null; this.pendingIncomingIceUpdates = null; @@ -944,20 +967,24 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo private void sendMessage(@NonNull WebRtcViewModel.State state, - @NonNull Recipient recipient, - boolean localVideoEnabled, boolean remoteVideoEnabled, - boolean bluetoothAvailable, boolean microphoneEnabled) + @NonNull Recipient recipient, + @NonNull CameraState localCameraState, + boolean remoteVideoEnabled, + boolean bluetoothAvailable, + boolean microphoneEnabled) { - EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled)); + EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled)); } private void sendMessage(@NonNull WebRtcViewModel.State state, - @NonNull Recipient recipient, - @NonNull IdentityKey identityKey, - boolean localVideoEnabled, boolean remoteVideoEnabled, - boolean bluetoothAvailable, boolean microphoneEnabled) + @NonNull Recipient recipient, + @NonNull IdentityKey identityKey, + @NonNull CameraState localCameraState, + boolean remoteVideoEnabled, + boolean bluetoothAvailable, + boolean microphoneEnabled) { - EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, identityKey, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled)); + EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, identityKey, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled)); } private ListenableFutureTask sendMessage(@NonNull final Recipient recipient, diff --git a/src/org/thoughtcrime/securesms/webrtc/CameraState.java b/src/org/thoughtcrime/securesms/webrtc/CameraState.java new file mode 100644 index 000000000..c5b0e2572 --- /dev/null +++ b/src/org/thoughtcrime/securesms/webrtc/CameraState.java @@ -0,0 +1,32 @@ +package org.thoughtcrime.securesms.webrtc; + +import android.support.annotation.NonNull; + +public class CameraState { + + public static final CameraState UNKNOWN = new CameraState(Direction.NONE, 0); + + private final Direction activeDirection; + private final int cameraCount; + + public CameraState(@NonNull Direction activeDirection, int cameraCount) { + this.activeDirection = activeDirection; + this.cameraCount = cameraCount; + } + + public int getCameraCount() { + return cameraCount; + } + + public Direction getActiveDirection() { + return activeDirection; + } + + public boolean isEnabled() { + return this.activeDirection != Direction.NONE; + } + + public enum Direction { + FRONT, BACK, NONE, PENDING + } +} diff --git a/src/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.java b/src/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.java index 0e63b28c1..f48c39990 100644 --- a/src/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.java +++ b/src/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.java @@ -21,7 +21,6 @@ import org.webrtc.PeerConnection; import org.webrtc.PeerConnectionFactory; import org.webrtc.SdpObserver; import org.webrtc.SessionDescription; -import org.webrtc.VideoCapturer; import org.webrtc.VideoRenderer; import org.webrtc.VideoSource; import org.webrtc.VideoTrack; @@ -30,6 +29,11 @@ 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(); @@ -38,25 +42,17 @@ public class PeerConnectionWrapper { @NonNull private final PeerConnection peerConnection; @NonNull private final AudioTrack audioTrack; @NonNull private final AudioSource audioSource; - - @Nullable private final VideoCapturer videoCapturer; - @Nullable private final VideoCapturer videoCapturerRear; + @NonNull private final Camera camera; @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, - @NonNull PeerConnection.Observer observer, - @NonNull VideoRenderer.Callbacks localRenderer, + public PeerConnectionWrapper(@NonNull Context context, + @NonNull PeerConnectionFactory factory, + @NonNull PeerConnection.Observer observer, + @NonNull VideoRenderer.Callbacks localRenderer, @NonNull List turnServers, - boolean hideIp) + @NonNull CameraEventListener cameraEventListener, + boolean hideIp) { List iceServers = new LinkedList<>(); iceServers.add(STUN_SERVER); @@ -80,69 +76,42 @@ public class PeerConnectionWrapper { this.peerConnection.setAudioPlayout(false); this.peerConnection.setAudioRecording(false); - this.videoCapturer = createVideoCapturer(context, false); - this.videoCapturerRear = createVideoCapturer(context, true); - - this.videoCapturerActive = videoCapturer; - - this.mediaStream = factory.createLocalMediaStream("ARDAMS"); + MediaStream mediaStream = factory.createLocalMediaStream("ARDAMS"); this.audioSource = factory.createAudioSource(audioConstraints); this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource); this.audioTrack.setEnabled(false); - this.mediaStream.addTrack(audioTrack); + mediaStream.addTrack(audioTrack); - if (videoCapturer != null) { - this.videoSource = factory.createVideoSource(videoCapturer); + this.camera = new Camera(context, cameraEventListener); + + if (camera.capturer != null) { + this.videoSource = factory.createVideoSource(camera.capturer); this.videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource); - this.videoTrackActive = videoTrack; - this.videoTrack.addRenderer(new VideoRenderer(localRenderer)); this.videoTrack.setEnabled(false); - this.mediaStream.addTrack(videoTrack); + 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.videoTrackActive != null) { - this.videoTrackActive.setEnabled(enabled); - } - - if (this.videoCapturerActive != null) { - try { - if (enabled) this.videoCapturerActive.startCapture(1280, 720, 30); - else this.videoCapturerActive.stopCapture(); - } catch (InterruptedException e) { - Log.w(TAG, e); - } + if (this.videoTrack != null) { + this.videoTrack.setEnabled(enabled); } + camera.setEnabled(enabled); } - 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 flipCamera() { + camera.flip(); + } + + public CameraState getCameraState() { + return new CameraState(camera.getActiveDirection(), camera.getCount()); } public void setCommunicationMode() { @@ -294,32 +263,12 @@ public class PeerConnectionWrapper { } public void dispose() { - if (this.videoCapturer != null) { - try { - this.videoCapturer.stopCapture(); - } catch (InterruptedException e) { - Log.w(TAG, e); - } - this.videoCapturer.dispose(); - } - - if (this.videoCapturerRear != null) { - try { - this.videoCapturerRear.stopCapture(); - } catch (InterruptedException e) { - Log.w(TAG, e); - } - this.videoCapturerRear.dispose(); - } + this.camera.dispose(); if (this.videoSource != null) { this.videoSource.dispose(); } - if (this.videoSourceRear != null) { - this.videoSourceRear.dispose(); - } - this.audioSource.dispose(); this.peerConnection.close(); this.peerConnection.dispose(); @@ -329,57 +278,6 @@ public class PeerConnectionWrapper { return this.peerConnection.addIceCandidate(candidate); } - private @Nullable CameraVideoCapturer createVideoCapturer(@NonNull Context context, boolean rear) { - boolean camera2EnumeratorIsSupported = false; - try { - camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context); - } catch (final Throwable throwable) { - Log.w(TAG, "Camera2Enumator.isSupport() threw.", throwable); - } - - Log.w(TAG, "Camera2 enumerator supported: " + camera2EnumeratorIsSupported); - CameraEnumerator enumerator; - - if (camera2EnumeratorIsSupported) enumerator = new Camera2Enumerator(context); - else enumerator = new Camera1Enumerator(true); - - String[] deviceNames = enumerator.getDeviceNames(); - - for (String deviceName : deviceNames) { - 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 " + direction + " facing capturer: " + deviceName); - - return videoCapturer; - } - } - } - - for (String deviceName : deviceNames) { - 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); - - if (videoCapturer != null) { - Log.w(TAG, "Found other facing capturer: " + deviceName); - return videoCapturer; - } - } - } - - Log.w(TAG, "Video capture not supported!"); - return null; - } private SessionDescription correctSessionDescription(SessionDescription sessionDescription) { String updatedSdp = sessionDescription.description.replaceAll("(a=fmtp:111 ((?!cbr=).)*)\r?\n", "$1;cbr=1\r\n"); @@ -397,4 +295,125 @@ public class PeerConnectionWrapper { 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.w(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); + } }