package eu.siacs.conversations.xmpp.jingle; import android.content.Context; import android.util.Log; import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import org.webrtc.Camera2Enumerator; import org.webrtc.CameraEnumerationAndroid; import org.webrtc.CameraEnumerator; import org.webrtc.CameraVideoCapturer; import org.webrtc.EglBase; import org.webrtc.PeerConnectionFactory; import org.webrtc.SurfaceTextureHelper; import org.webrtc.VideoSource; import java.util.ArrayList; import java.util.Collections; import java.util.Set; import javax.annotation.Nullable; import eu.siacs.conversations.Config; class VideoSourceWrapper { private static final int CAPTURING_RESOLUTION = 1920; private static final int CAPTURING_MAX_FRAME_RATE = 30; private final CameraVideoCapturer cameraVideoCapturer; private final CameraEnumerationAndroid.CaptureFormat captureFormat; private final Set availableCameras; private boolean isFrontCamera = false; private VideoSource videoSource; VideoSourceWrapper( CameraVideoCapturer cameraVideoCapturer, CameraEnumerationAndroid.CaptureFormat captureFormat, Set cameras) { this.cameraVideoCapturer = cameraVideoCapturer; this.captureFormat = captureFormat; this.availableCameras = cameras; } private int getFrameRate() { return Math.max( captureFormat.framerate.min, Math.min(CAPTURING_MAX_FRAME_RATE, captureFormat.framerate.max)); } public void initialize( final PeerConnectionFactory peerConnectionFactory, final Context context, final EglBase.Context eglBaseContext) { final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("webrtc", eglBaseContext); this.videoSource = peerConnectionFactory.createVideoSource(false); this.cameraVideoCapturer.initialize( surfaceTextureHelper, context, this.videoSource.getCapturerObserver()); } public VideoSource getVideoSource() { final VideoSource videoSource = this.videoSource; if (videoSource == null) { throw new IllegalStateException("VideoSourceWrapper was not initialized"); } return videoSource; } public void startCapture() { final int frameRate = getFrameRate(); Log.d( Config.LOGTAG, String.format( "start capturing at %dx%d@%d", captureFormat.width, captureFormat.height, frameRate)); this.cameraVideoCapturer.startCapture(captureFormat.width, captureFormat.height, frameRate); } public void stopCapture() throws InterruptedException { this.cameraVideoCapturer.stopCapture(); } public void dispose() { this.cameraVideoCapturer.dispose(); if (this.videoSource != null) { this.videoSource.dispose(); } } public ListenableFuture switchCamera() { final SettableFuture future = SettableFuture.create(); this.cameraVideoCapturer.switchCamera( new CameraVideoCapturer.CameraSwitchHandler() { @Override public void onCameraSwitchDone(final boolean isFrontCamera) { VideoSourceWrapper.this.isFrontCamera = isFrontCamera; future.set(isFrontCamera); } @Override public void onCameraSwitchError(final String message) { future.setException( new IllegalStateException( String.format("Unable to switch camera %s", message))); } }); return future; } public boolean isFrontCamera() { return this.isFrontCamera; } public boolean isCameraSwitchable() { return this.availableCameras.size() > 1; } public static class Factory { final Context context; public Factory(final Context context) { this.context = context; } public Optional create() { final CameraEnumerator enumerator = new Camera2Enumerator(context); final Set deviceNames = ImmutableSet.copyOf(enumerator.getDeviceNames()); for (final String deviceName : deviceNames) { if (isFrontFacing(enumerator, deviceName)) { final VideoSourceWrapper videoSourceWrapper = of(enumerator, deviceName, deviceNames); if (videoSourceWrapper == null) { return Optional.absent(); } videoSourceWrapper.isFrontCamera = true; return Optional.of(videoSourceWrapper); } } if (deviceNames.size() == 0) { return Optional.absent(); } else { return Optional.fromNullable( of(enumerator, Iterables.get(deviceNames, 0), deviceNames)); } } @Nullable private VideoSourceWrapper of( final CameraEnumerator enumerator, final String deviceName, final Set availableCameras) { final CameraVideoCapturer capturer = enumerator.createCapturer(deviceName, null); if (capturer == null) { return null; } final ArrayList choices = new ArrayList<>(enumerator.getSupportedFormats(deviceName)); Collections.sort(choices, (a, b) -> b.width - a.width); for (final CameraEnumerationAndroid.CaptureFormat captureFormat : choices) { if (captureFormat.width <= CAPTURING_RESOLUTION) { return new VideoSourceWrapper(capturer, captureFormat, availableCameras); } } return null; } private static boolean isFrontFacing( final CameraEnumerator cameraEnumerator, final String deviceName) { try { return cameraEnumerator.isFrontFacing(deviceName); } catch (final NullPointerException e) { return false; } } } }