2016-08-11 01:00:37 -07:00
|
|
|
/*
|
|
|
|
|
* Copyright 2016 The WebRTC project authors. All Rights Reserved.
|
|
|
|
|
*
|
|
|
|
|
* Use of this source code is governed by a BSD-style license
|
|
|
|
|
* that can be found in the LICENSE file in the root of the source
|
|
|
|
|
* tree. An additional intellectual property rights grant can be found
|
|
|
|
|
* in the file PATENTS. All contributing project authors may
|
|
|
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package org.webrtc;
|
|
|
|
|
|
|
|
|
|
import android.content.Context;
|
2017-04-27 13:38:29 -07:00
|
|
|
import android.media.MediaRecorder;
|
2016-08-11 01:00:37 -07:00
|
|
|
import android.os.Handler;
|
2016-08-19 03:02:35 -07:00
|
|
|
import android.os.Looper;
|
2018-03-22 13:32:44 +01:00
|
|
|
import javax.annotation.Nullable;
|
2016-08-11 01:00:37 -07:00
|
|
|
import java.util.Arrays;
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("deprecation")
|
2017-02-20 07:04:03 -08:00
|
|
|
abstract class CameraCapturer implements CameraVideoCapturer {
|
2016-08-15 06:19:34 -07:00
|
|
|
enum SwitchState {
|
|
|
|
|
IDLE, // No switch requested.
|
|
|
|
|
PENDING, // Waiting for previous capture session to open.
|
|
|
|
|
IN_PROGRESS, // Waiting for new switched capture session to start.
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-27 13:38:29 -07:00
|
|
|
enum MediaRecorderState {
|
|
|
|
|
IDLE, // No media recording update (add or remove) requested.
|
|
|
|
|
IDLE_TO_ACTIVE, // Waiting for new capture session with added MediaRecorder surface to start.
|
|
|
|
|
ACTIVE_TO_IDLE, // Waiting for new capture session with removed MediaRecorder surface to start.
|
|
|
|
|
ACTIVE, // MediaRecorder was successfully added to camera pipeline.
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-11 01:00:37 -07:00
|
|
|
private static final String TAG = "CameraCapturer";
|
|
|
|
|
private final static int MAX_OPEN_CAMERA_ATTEMPTS = 3;
|
|
|
|
|
private final static int OPEN_CAMERA_DELAY_MS = 500;
|
2016-08-19 03:02:35 -07:00
|
|
|
private final static int OPEN_CAMERA_TIMEOUT = 10000;
|
2016-08-11 01:00:37 -07:00
|
|
|
|
|
|
|
|
private final CameraEnumerator cameraEnumerator;
|
2018-03-22 13:32:44 +01:00
|
|
|
@Nullable private final CameraEventsHandler eventsHandler;
|
2016-08-19 03:02:35 -07:00
|
|
|
private final Handler uiThreadHandler;
|
2016-08-11 01:00:37 -07:00
|
|
|
|
2018-03-22 13:32:44 +01:00
|
|
|
@Nullable
|
2016-08-11 01:00:37 -07:00
|
|
|
private final CameraSession.CreateSessionCallback createSessionCallback =
|
|
|
|
|
new CameraSession.CreateSessionCallback() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onDone(CameraSession session) {
|
2016-09-19 04:37:16 -07:00
|
|
|
checkIsOnCameraThread();
|
2017-04-27 13:38:29 -07:00
|
|
|
Logging.d(TAG,
|
|
|
|
|
"Create session done. Switch state: " + switchState
|
|
|
|
|
+ ". MediaRecorder state: " + mediaRecorderState);
|
2016-08-19 03:02:35 -07:00
|
|
|
uiThreadHandler.removeCallbacks(openCameraTimeoutRunnable);
|
2016-08-11 01:00:37 -07:00
|
|
|
synchronized (stateLock) {
|
2016-09-21 07:44:50 -07:00
|
|
|
capturerObserver.onCapturerStarted(true /* success */);
|
2016-08-11 01:00:37 -07:00
|
|
|
sessionOpening = false;
|
|
|
|
|
currentSession = session;
|
2016-09-21 06:08:53 -07:00
|
|
|
cameraStatistics = new CameraStatistics(surfaceHelper, eventsHandler);
|
|
|
|
|
firstFrameObserved = false;
|
2016-08-11 01:00:37 -07:00
|
|
|
stateLock.notifyAll();
|
|
|
|
|
|
2016-08-15 06:19:34 -07:00
|
|
|
if (switchState == SwitchState.IN_PROGRESS) {
|
|
|
|
|
if (switchEventsHandler != null) {
|
|
|
|
|
switchEventsHandler.onCameraSwitchDone(cameraEnumerator.isFrontFacing(cameraName));
|
|
|
|
|
switchEventsHandler = null;
|
|
|
|
|
}
|
|
|
|
|
switchState = SwitchState.IDLE;
|
|
|
|
|
} else if (switchState == SwitchState.PENDING) {
|
|
|
|
|
switchState = SwitchState.IDLE;
|
|
|
|
|
switchCameraInternal(switchEventsHandler);
|
2016-08-11 01:00:37 -07:00
|
|
|
}
|
2017-04-27 13:38:29 -07:00
|
|
|
|
|
|
|
|
if (mediaRecorderState == MediaRecorderState.IDLE_TO_ACTIVE
|
|
|
|
|
|| mediaRecorderState == MediaRecorderState.ACTIVE_TO_IDLE) {
|
|
|
|
|
if (mediaRecorderEventsHandler != null) {
|
|
|
|
|
mediaRecorderEventsHandler.onMediaRecorderSuccess();
|
|
|
|
|
mediaRecorderEventsHandler = null;
|
|
|
|
|
}
|
|
|
|
|
if (mediaRecorderState == MediaRecorderState.IDLE_TO_ACTIVE) {
|
|
|
|
|
mediaRecorderState = MediaRecorderState.ACTIVE;
|
|
|
|
|
} else {
|
|
|
|
|
mediaRecorderState = MediaRecorderState.IDLE;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-08-11 01:00:37 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2017-01-18 03:22:16 -08:00
|
|
|
public void onFailure(CameraSession.FailureType failureType, String error) {
|
2016-09-19 04:37:16 -07:00
|
|
|
checkIsOnCameraThread();
|
2016-08-19 03:02:35 -07:00
|
|
|
uiThreadHandler.removeCallbacks(openCameraTimeoutRunnable);
|
2016-08-11 01:00:37 -07:00
|
|
|
synchronized (stateLock) {
|
2016-09-21 07:44:50 -07:00
|
|
|
capturerObserver.onCapturerStarted(false /* success */);
|
2016-08-11 01:00:37 -07:00
|
|
|
openAttemptsRemaining--;
|
|
|
|
|
|
|
|
|
|
if (openAttemptsRemaining <= 0) {
|
|
|
|
|
Logging.w(TAG, "Opening camera failed, passing: " + error);
|
|
|
|
|
sessionOpening = false;
|
|
|
|
|
stateLock.notifyAll();
|
|
|
|
|
|
2016-08-15 06:19:34 -07:00
|
|
|
if (switchState != SwitchState.IDLE) {
|
|
|
|
|
if (switchEventsHandler != null) {
|
|
|
|
|
switchEventsHandler.onCameraSwitchError(error);
|
|
|
|
|
switchEventsHandler = null;
|
|
|
|
|
}
|
|
|
|
|
switchState = SwitchState.IDLE;
|
2016-08-11 01:00:37 -07:00
|
|
|
}
|
|
|
|
|
|
2017-04-27 13:38:29 -07:00
|
|
|
if (mediaRecorderState != MediaRecorderState.IDLE) {
|
|
|
|
|
if (mediaRecorderEventsHandler != null) {
|
|
|
|
|
mediaRecorderEventsHandler.onMediaRecorderError(error);
|
|
|
|
|
mediaRecorderEventsHandler = null;
|
|
|
|
|
}
|
|
|
|
|
mediaRecorderState = MediaRecorderState.IDLE;
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-18 03:22:16 -08:00
|
|
|
if (failureType == CameraSession.FailureType.DISCONNECTED) {
|
|
|
|
|
eventsHandler.onCameraDisconnected();
|
|
|
|
|
} else {
|
|
|
|
|
eventsHandler.onCameraError(error);
|
|
|
|
|
}
|
2016-08-11 01:00:37 -07:00
|
|
|
} else {
|
|
|
|
|
Logging.w(TAG, "Opening camera failed, retry: " + error);
|
2017-04-27 13:38:29 -07:00
|
|
|
createSessionInternal(OPEN_CAMERA_DELAY_MS, null /* mediaRecorder */);
|
2016-08-11 01:00:37 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2018-03-22 13:32:44 +01:00
|
|
|
@Nullable
|
2016-09-19 04:37:16 -07:00
|
|
|
private final CameraSession.Events cameraSessionEventsHandler = new CameraSession.Events() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onCameraOpening() {
|
|
|
|
|
checkIsOnCameraThread();
|
|
|
|
|
synchronized (stateLock) {
|
|
|
|
|
if (currentSession != null) {
|
|
|
|
|
Logging.w(TAG, "onCameraOpening while session was open.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
eventsHandler.onCameraOpening(cameraName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onCameraError(CameraSession session, String error) {
|
|
|
|
|
checkIsOnCameraThread();
|
|
|
|
|
synchronized (stateLock) {
|
|
|
|
|
if (session != currentSession) {
|
|
|
|
|
Logging.w(TAG, "onCameraError from another session: " + error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
eventsHandler.onCameraError(error);
|
|
|
|
|
stopCapture();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-06 00:59:10 -07:00
|
|
|
@Override
|
|
|
|
|
public void onCameraDisconnected(CameraSession session) {
|
|
|
|
|
checkIsOnCameraThread();
|
|
|
|
|
synchronized (stateLock) {
|
|
|
|
|
if (session != currentSession) {
|
|
|
|
|
Logging.w(TAG, "onCameraDisconnected from another session.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
eventsHandler.onCameraDisconnected();
|
|
|
|
|
stopCapture();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-19 04:37:16 -07:00
|
|
|
@Override
|
|
|
|
|
public void onCameraClosed(CameraSession session) {
|
|
|
|
|
checkIsOnCameraThread();
|
|
|
|
|
synchronized (stateLock) {
|
|
|
|
|
if (session != currentSession && currentSession != null) {
|
|
|
|
|
Logging.d(TAG, "onCameraClosed from another session.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
eventsHandler.onCameraClosed();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-23 08:50:23 -07:00
|
|
|
@Override
|
|
|
|
|
public void onFrameCaptured(CameraSession session, VideoFrame frame) {
|
|
|
|
|
checkIsOnCameraThread();
|
|
|
|
|
synchronized (stateLock) {
|
|
|
|
|
if (session != currentSession) {
|
|
|
|
|
Logging.w(TAG, "onTextureFrameCaptured from another session.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!firstFrameObserved) {
|
|
|
|
|
eventsHandler.onFirstFrameAvailable();
|
|
|
|
|
firstFrameObserved = true;
|
|
|
|
|
}
|
|
|
|
|
cameraStatistics.addFrame();
|
|
|
|
|
capturerObserver.onFrameCaptured(frame);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-09-19 04:37:16 -07:00
|
|
|
};
|
|
|
|
|
|
2016-08-19 03:02:35 -07:00
|
|
|
private final Runnable openCameraTimeoutRunnable = new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
eventsHandler.onCameraError("Camera failed to start within timeout.");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2016-08-11 01:00:37 -07:00
|
|
|
// Initialized on initialize
|
|
|
|
|
// -------------------------
|
2018-03-22 13:32:44 +01:00
|
|
|
@Nullable private Handler cameraThreadHandler;
|
2016-08-11 01:00:37 -07:00
|
|
|
private Context applicationContext;
|
|
|
|
|
private CapturerObserver capturerObserver;
|
2018-03-22 13:32:44 +01:00
|
|
|
@Nullable private SurfaceTextureHelper surfaceHelper;
|
2016-08-11 01:00:37 -07:00
|
|
|
|
|
|
|
|
private final Object stateLock = new Object();
|
|
|
|
|
private boolean sessionOpening; /* guarded by stateLock */
|
2018-03-22 13:32:44 +01:00
|
|
|
@Nullable private CameraSession currentSession; /* guarded by stateLock */
|
2016-08-11 01:00:37 -07:00
|
|
|
private String cameraName; /* guarded by stateLock */
|
|
|
|
|
private int width; /* guarded by stateLock */
|
|
|
|
|
private int height; /* guarded by stateLock */
|
|
|
|
|
private int framerate; /* guarded by stateLock */
|
|
|
|
|
private int openAttemptsRemaining; /* guarded by stateLock */
|
2016-08-15 06:19:34 -07:00
|
|
|
private SwitchState switchState = SwitchState.IDLE; /* guarded by stateLock */
|
2018-03-22 13:32:44 +01:00
|
|
|
@Nullable private CameraSwitchHandler switchEventsHandler; /* guarded by stateLock */
|
2016-09-19 04:37:16 -07:00
|
|
|
// Valid from onDone call until stopCapture, otherwise null.
|
2018-03-22 13:32:44 +01:00
|
|
|
@Nullable private CameraStatistics cameraStatistics; /* guarded by stateLock */
|
2016-09-19 04:37:16 -07:00
|
|
|
private boolean firstFrameObserved; /* guarded by stateLock */
|
2016-08-11 01:00:37 -07:00
|
|
|
|
2017-04-27 13:38:29 -07:00
|
|
|
// Variables used on camera thread - do not require stateLock synchronization.
|
|
|
|
|
private MediaRecorderState mediaRecorderState = MediaRecorderState.IDLE;
|
2018-03-22 13:32:44 +01:00
|
|
|
@Nullable private MediaRecorderHandler mediaRecorderEventsHandler;
|
2017-04-27 13:38:29 -07:00
|
|
|
|
2018-03-22 13:32:44 +01:00
|
|
|
public CameraCapturer(String cameraName, @Nullable CameraEventsHandler eventsHandler,
|
|
|
|
|
CameraEnumerator cameraEnumerator) {
|
2016-08-11 01:00:37 -07:00
|
|
|
if (eventsHandler == null) {
|
|
|
|
|
eventsHandler = new CameraEventsHandler() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onCameraError(String errorDescription) {}
|
|
|
|
|
@Override
|
2016-10-06 00:59:10 -07:00
|
|
|
public void onCameraDisconnected() {}
|
|
|
|
|
@Override
|
2016-08-11 01:00:37 -07:00
|
|
|
public void onCameraFreezed(String errorDescription) {}
|
|
|
|
|
@Override
|
2016-09-14 05:36:20 -07:00
|
|
|
public void onCameraOpening(String cameraName) {}
|
2016-08-11 01:00:37 -07:00
|
|
|
@Override
|
|
|
|
|
public void onFirstFrameAvailable() {}
|
|
|
|
|
@Override
|
|
|
|
|
public void onCameraClosed() {}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.eventsHandler = eventsHandler;
|
|
|
|
|
this.cameraEnumerator = cameraEnumerator;
|
|
|
|
|
this.cameraName = cameraName;
|
2016-08-19 03:02:35 -07:00
|
|
|
uiThreadHandler = new Handler(Looper.getMainLooper());
|
2016-08-11 01:00:37 -07:00
|
|
|
|
|
|
|
|
final String[] deviceNames = cameraEnumerator.getDeviceNames();
|
|
|
|
|
|
|
|
|
|
if (deviceNames.length == 0) {
|
|
|
|
|
throw new RuntimeException("No cameras attached.");
|
|
|
|
|
}
|
|
|
|
|
if (!Arrays.asList(deviceNames).contains(this.cameraName)) {
|
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
|
"Camera name " + this.cameraName + " does not match any known camera device.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2018-03-22 13:32:44 +01:00
|
|
|
public void initialize(@Nullable SurfaceTextureHelper surfaceTextureHelper,
|
|
|
|
|
Context applicationContext, CapturerObserver capturerObserver) {
|
2016-08-11 01:00:37 -07:00
|
|
|
this.applicationContext = applicationContext;
|
|
|
|
|
this.capturerObserver = capturerObserver;
|
|
|
|
|
this.surfaceHelper = surfaceTextureHelper;
|
|
|
|
|
this.cameraThreadHandler =
|
|
|
|
|
surfaceTextureHelper == null ? null : surfaceTextureHelper.getHandler();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void startCapture(int width, int height, int framerate) {
|
|
|
|
|
Logging.d(TAG, "startCapture: " + width + "x" + height + "@" + framerate);
|
2017-01-10 04:30:31 -08:00
|
|
|
if (applicationContext == null) {
|
|
|
|
|
throw new RuntimeException("CameraCapturer must be initialized before calling startCapture.");
|
|
|
|
|
}
|
2016-08-11 01:00:37 -07:00
|
|
|
|
|
|
|
|
synchronized (stateLock) {
|
|
|
|
|
if (sessionOpening || currentSession != null) {
|
|
|
|
|
Logging.w(TAG, "Session already open");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.width = width;
|
|
|
|
|
this.height = height;
|
|
|
|
|
this.framerate = framerate;
|
|
|
|
|
|
|
|
|
|
sessionOpening = true;
|
|
|
|
|
openAttemptsRemaining = MAX_OPEN_CAMERA_ATTEMPTS;
|
2017-04-27 13:38:29 -07:00
|
|
|
createSessionInternal(0, null /* mediaRecorder */);
|
2016-08-11 01:00:37 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-27 13:38:29 -07:00
|
|
|
private void createSessionInternal(int delayMs, final MediaRecorder mediaRecorder) {
|
2016-08-19 03:02:35 -07:00
|
|
|
uiThreadHandler.postDelayed(openCameraTimeoutRunnable, delayMs + OPEN_CAMERA_TIMEOUT);
|
2016-08-11 01:00:37 -07:00
|
|
|
cameraThreadHandler.postDelayed(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
2016-09-19 04:37:16 -07:00
|
|
|
createCameraSession(createSessionCallback, cameraSessionEventsHandler, applicationContext,
|
2017-04-27 13:38:29 -07:00
|
|
|
surfaceHelper, mediaRecorder, cameraName, width, height, framerate);
|
2016-08-11 01:00:37 -07:00
|
|
|
}
|
|
|
|
|
}, delayMs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void stopCapture() {
|
|
|
|
|
Logging.d(TAG, "Stop capture");
|
|
|
|
|
|
|
|
|
|
synchronized (stateLock) {
|
|
|
|
|
while (sessionOpening) {
|
|
|
|
|
Logging.d(TAG, "Stop capture: Waiting for session to open");
|
2018-01-30 14:17:56 +01:00
|
|
|
try {
|
|
|
|
|
stateLock.wait();
|
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
Logging.w(TAG, "Stop capture interrupted while waiting for the session to open.");
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-08-11 01:00:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (currentSession != null) {
|
2016-09-21 07:44:50 -07:00
|
|
|
Logging.d(TAG, "Stop capture: Nulling session");
|
2016-09-19 04:37:16 -07:00
|
|
|
cameraStatistics.release();
|
|
|
|
|
cameraStatistics = null;
|
2016-09-21 07:44:50 -07:00
|
|
|
final CameraSession oldSession = currentSession;
|
|
|
|
|
cameraThreadHandler.post(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
oldSession.stop();
|
|
|
|
|
}
|
|
|
|
|
});
|
2016-08-11 01:00:37 -07:00
|
|
|
currentSession = null;
|
2016-09-19 04:37:16 -07:00
|
|
|
capturerObserver.onCapturerStopped();
|
2016-08-11 01:00:37 -07:00
|
|
|
} else {
|
|
|
|
|
Logging.d(TAG, "Stop capture: No session open");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Logging.d(TAG, "Stop capture done");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void changeCaptureFormat(int width, int height, int framerate) {
|
|
|
|
|
Logging.d(TAG, "changeCaptureFormat: " + width + "x" + height + "@" + framerate);
|
|
|
|
|
synchronized (stateLock) {
|
|
|
|
|
stopCapture();
|
|
|
|
|
startCapture(width, height, framerate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void dispose() {
|
|
|
|
|
Logging.d(TAG, "dispose");
|
|
|
|
|
stopCapture();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void switchCamera(final CameraSwitchHandler switchEventsHandler) {
|
|
|
|
|
Logging.d(TAG, "switchCamera");
|
2016-08-15 06:19:34 -07:00
|
|
|
cameraThreadHandler.post(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
switchCameraInternal(switchEventsHandler);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-27 13:38:29 -07:00
|
|
|
@Override
|
|
|
|
|
public void addMediaRecorderToCamera(
|
|
|
|
|
final MediaRecorder mediaRecorder, final MediaRecorderHandler mediaRecoderEventsHandler) {
|
|
|
|
|
Logging.d(TAG, "addMediaRecorderToCamera");
|
|
|
|
|
cameraThreadHandler.post(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
updateMediaRecorderInternal(mediaRecorder, mediaRecoderEventsHandler);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void removeMediaRecorderFromCamera(final MediaRecorderHandler mediaRecoderEventsHandler) {
|
|
|
|
|
Logging.d(TAG, "removeMediaRecorderFromCamera");
|
|
|
|
|
cameraThreadHandler.post(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
updateMediaRecorderInternal(null /* mediaRecorder */, mediaRecoderEventsHandler);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-31 18:50:52 -07:00
|
|
|
@Override
|
|
|
|
|
public boolean isScreencast() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-12 07:35:18 -07:00
|
|
|
public void printStackTrace() {
|
|
|
|
|
Thread cameraThread = null;
|
|
|
|
|
if (cameraThreadHandler != null) {
|
|
|
|
|
cameraThread = cameraThreadHandler.getLooper().getThread();
|
|
|
|
|
}
|
|
|
|
|
if (cameraThread != null) {
|
|
|
|
|
StackTraceElement[] cameraStackTrace = cameraThread.getStackTrace();
|
|
|
|
|
if (cameraStackTrace.length > 0) {
|
|
|
|
|
Logging.d(TAG, "CameraCapturer stack trace:");
|
|
|
|
|
for (StackTraceElement traceElem : cameraStackTrace) {
|
|
|
|
|
Logging.d(TAG, traceElem.toString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-22 13:32:44 +01:00
|
|
|
private void reportCameraSwitchError(
|
|
|
|
|
String error, @Nullable CameraSwitchHandler switchEventsHandler) {
|
2017-04-27 13:38:29 -07:00
|
|
|
Logging.e(TAG, error);
|
|
|
|
|
if (switchEventsHandler != null) {
|
|
|
|
|
switchEventsHandler.onCameraSwitchError(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-22 13:32:44 +01:00
|
|
|
private void switchCameraInternal(@Nullable final CameraSwitchHandler switchEventsHandler) {
|
2016-08-15 06:19:34 -07:00
|
|
|
Logging.d(TAG, "switchCamera internal");
|
2016-08-11 01:00:37 -07:00
|
|
|
|
|
|
|
|
final String[] deviceNames = cameraEnumerator.getDeviceNames();
|
|
|
|
|
|
|
|
|
|
if (deviceNames.length < 2) {
|
|
|
|
|
if (switchEventsHandler != null) {
|
|
|
|
|
switchEventsHandler.onCameraSwitchError("No camera to switch to.");
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
synchronized (stateLock) {
|
2016-08-15 06:19:34 -07:00
|
|
|
if (switchState != SwitchState.IDLE) {
|
2017-04-27 13:38:29 -07:00
|
|
|
reportCameraSwitchError("Camera switch already in progress.", switchEventsHandler);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (mediaRecorderState != MediaRecorderState.IDLE) {
|
|
|
|
|
reportCameraSwitchError("switchCamera: media recording is active", switchEventsHandler);
|
2016-08-11 01:00:37 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2016-09-21 06:08:53 -07:00
|
|
|
if (!sessionOpening && currentSession == null) {
|
2017-04-27 13:38:29 -07:00
|
|
|
reportCameraSwitchError("switchCamera: camera is not running.", switchEventsHandler);
|
2016-09-21 06:08:53 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-15 06:19:34 -07:00
|
|
|
this.switchEventsHandler = switchEventsHandler;
|
2016-08-11 01:00:37 -07:00
|
|
|
if (sessionOpening) {
|
2016-08-15 06:19:34 -07:00
|
|
|
switchState = SwitchState.PENDING;
|
2016-08-11 01:00:37 -07:00
|
|
|
return;
|
2016-08-15 06:19:34 -07:00
|
|
|
} else {
|
|
|
|
|
switchState = SwitchState.IN_PROGRESS;
|
2016-08-11 01:00:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Logging.d(TAG, "switchCamera: Stopping session");
|
2016-09-21 06:08:53 -07:00
|
|
|
cameraStatistics.release();
|
|
|
|
|
cameraStatistics = null;
|
2016-09-21 07:44:50 -07:00
|
|
|
final CameraSession oldSession = currentSession;
|
|
|
|
|
cameraThreadHandler.post(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
oldSession.stop();
|
|
|
|
|
}
|
|
|
|
|
});
|
2016-08-11 01:00:37 -07:00
|
|
|
currentSession = null;
|
|
|
|
|
|
|
|
|
|
int cameraNameIndex = Arrays.asList(deviceNames).indexOf(cameraName);
|
|
|
|
|
cameraName = deviceNames[(cameraNameIndex + 1) % deviceNames.length];
|
|
|
|
|
|
|
|
|
|
sessionOpening = true;
|
|
|
|
|
openAttemptsRemaining = 1;
|
2017-04-27 13:38:29 -07:00
|
|
|
createSessionInternal(0, null /* mediaRecorder */);
|
2016-08-11 01:00:37 -07:00
|
|
|
}
|
|
|
|
|
Logging.d(TAG, "switchCamera done");
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-27 13:38:29 -07:00
|
|
|
private void reportUpdateMediaRecorderError(
|
2018-03-22 13:32:44 +01:00
|
|
|
String error, @Nullable MediaRecorderHandler mediaRecoderEventsHandler) {
|
2017-04-27 13:38:29 -07:00
|
|
|
checkIsOnCameraThread();
|
|
|
|
|
Logging.e(TAG, error);
|
|
|
|
|
if (mediaRecoderEventsHandler != null) {
|
|
|
|
|
mediaRecoderEventsHandler.onMediaRecorderError(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void updateMediaRecorderInternal(
|
2018-03-22 13:32:44 +01:00
|
|
|
@Nullable MediaRecorder mediaRecorder, MediaRecorderHandler mediaRecoderEventsHandler) {
|
2017-04-27 13:38:29 -07:00
|
|
|
checkIsOnCameraThread();
|
|
|
|
|
boolean addMediaRecorder = (mediaRecorder != null);
|
|
|
|
|
Logging.d(TAG,
|
|
|
|
|
"updateMediaRecoderInternal internal. State: " + mediaRecorderState
|
|
|
|
|
+ ". Switch state: " + switchState + ". Add MediaRecorder: " + addMediaRecorder);
|
|
|
|
|
|
|
|
|
|
synchronized (stateLock) {
|
|
|
|
|
if ((addMediaRecorder && mediaRecorderState != MediaRecorderState.IDLE)
|
|
|
|
|
|| (!addMediaRecorder && mediaRecorderState != MediaRecorderState.ACTIVE)) {
|
|
|
|
|
reportUpdateMediaRecorderError(
|
|
|
|
|
"Incorrect state for MediaRecorder update.", mediaRecoderEventsHandler);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (switchState != SwitchState.IDLE) {
|
|
|
|
|
reportUpdateMediaRecorderError(
|
|
|
|
|
"MediaRecorder update while camera is switching.", mediaRecoderEventsHandler);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (currentSession == null) {
|
|
|
|
|
reportUpdateMediaRecorderError(
|
|
|
|
|
"MediaRecorder update while camera is closed.", mediaRecoderEventsHandler);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (sessionOpening) {
|
|
|
|
|
reportUpdateMediaRecorderError(
|
|
|
|
|
"MediaRecorder update while camera is still opening.", mediaRecoderEventsHandler);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.mediaRecorderEventsHandler = mediaRecoderEventsHandler;
|
|
|
|
|
mediaRecorderState =
|
|
|
|
|
addMediaRecorder ? MediaRecorderState.IDLE_TO_ACTIVE : MediaRecorderState.ACTIVE_TO_IDLE;
|
|
|
|
|
|
|
|
|
|
Logging.d(TAG, "updateMediaRecoder: Stopping session");
|
|
|
|
|
cameraStatistics.release();
|
|
|
|
|
cameraStatistics = null;
|
|
|
|
|
final CameraSession oldSession = currentSession;
|
|
|
|
|
cameraThreadHandler.post(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
oldSession.stop();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
currentSession = null;
|
|
|
|
|
|
|
|
|
|
sessionOpening = true;
|
|
|
|
|
openAttemptsRemaining = 1;
|
|
|
|
|
createSessionInternal(0, mediaRecorder);
|
|
|
|
|
}
|
|
|
|
|
Logging.d(TAG, "updateMediaRecoderInternal done");
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-19 04:37:16 -07:00
|
|
|
private void checkIsOnCameraThread() {
|
|
|
|
|
if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) {
|
|
|
|
|
Logging.e(TAG, "Check is on camera thread failed.");
|
|
|
|
|
throw new RuntimeException("Not on camera thread.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-11 01:00:37 -07:00
|
|
|
protected String getCameraName() {
|
|
|
|
|
synchronized (stateLock) {
|
|
|
|
|
return cameraName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
abstract protected void createCameraSession(
|
2016-09-19 04:37:16 -07:00
|
|
|
CameraSession.CreateSessionCallback createSessionCallback, CameraSession.Events events,
|
2017-04-27 13:38:29 -07:00
|
|
|
Context applicationContext, SurfaceTextureHelper surfaceTextureHelper,
|
|
|
|
|
MediaRecorder mediaRecoder, String cameraName, int width, int height, int framerate);
|
2016-08-11 01:00:37 -07:00
|
|
|
}
|