| Index: webrtc/api/android/java/src/org/webrtc/CameraCapturer.java
 | 
| diff --git a/webrtc/api/android/java/src/org/webrtc/CameraCapturer.java b/webrtc/api/android/java/src/org/webrtc/CameraCapturer.java
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..75712cc57031f255b4e19f92c021d83ab1bb0c34
 | 
| --- /dev/null
 | 
| +++ b/webrtc/api/android/java/src/org/webrtc/CameraCapturer.java
 | 
| @@ -0,0 +1,285 @@
 | 
| +/*
 | 
| + *  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 org.webrtc.CameraEnumerationAndroid.CaptureFormat;
 | 
| +
 | 
| +import android.content.Context;
 | 
| +import android.os.Handler;
 | 
| +import android.os.SystemClock;
 | 
| +
 | 
| +import java.util.Arrays;
 | 
| +import java.util.List;
 | 
| +
 | 
| +@SuppressWarnings("deprecation")
 | 
| +public abstract class CameraCapturer implements CameraVideoCapturer {
 | 
| +  private static final String TAG = "CameraCapturer";
 | 
| +  private final static int MAX_OPEN_CAMERA_ATTEMPTS = 3;
 | 
| +  private final static int OPEN_CAMERA_DELAY_MS = 500;
 | 
| +
 | 
| +  private final CameraEnumerator cameraEnumerator;
 | 
| +  private final CameraEventsHandler eventsHandler;
 | 
| +
 | 
| +  private final CameraSession.CreateSessionCallback createSessionCallback =
 | 
| +      new CameraSession.CreateSessionCallback() {
 | 
| +        @Override
 | 
| +        public void onDone(CameraSession session) {
 | 
| +          Logging.d(TAG, "Create session done");
 | 
| +
 | 
| +          synchronized (stateLock) {
 | 
| +            sessionOpening = false;
 | 
| +            currentSession = session;
 | 
| +            stateLock.notifyAll();
 | 
| +
 | 
| +            if (switchEventsHandler != null) {
 | 
| +              switchEventsHandler.onCameraSwitchDone(
 | 
| +                  cameraEnumerator.isFrontFacing(cameraName));
 | 
| +              switchEventsHandler = null;
 | 
| +            }
 | 
| +            switchInProgress = false;
 | 
| +          }
 | 
| +        }
 | 
| +
 | 
| +        @Override
 | 
| +        public void onFailure(String error) {
 | 
| +          synchronized (stateLock) {
 | 
| +            openAttemptsRemaining--;
 | 
| +
 | 
| +            if (openAttemptsRemaining <= 0) {
 | 
| +              Logging.w(TAG, "Opening camera failed, passing: " + error);
 | 
| +              sessionOpening = false;
 | 
| +              stateLock.notifyAll();
 | 
| +
 | 
| +              if (switchEventsHandler != null) {
 | 
| +                switchEventsHandler.onCameraSwitchError(error);
 | 
| +                switchEventsHandler = null;
 | 
| +              }
 | 
| +              switchInProgress = false;
 | 
| +
 | 
| +              eventsHandler.onCameraError(error);
 | 
| +            } else {
 | 
| +              Logging.w(TAG, "Opening camera failed, retry: " + error);
 | 
| +
 | 
| +              createSessionInternal(OPEN_CAMERA_DELAY_MS);
 | 
| +            }
 | 
| +          }
 | 
| +        }
 | 
| +      };
 | 
| +
 | 
| +  // Initialized on initialize
 | 
| +  // -------------------------
 | 
| +  // Use postOnCameraThread() instead of posting directly to the handler - this way all
 | 
| +  // callbacks with a specifed token can be removed at once.
 | 
| +  private Handler cameraThreadHandler;
 | 
| +  private Context applicationContext;
 | 
| +  private CapturerObserver capturerObserver;
 | 
| +  private SurfaceTextureHelper surfaceHelper;
 | 
| +
 | 
| +  private final Object stateLock = new Object();
 | 
| +  private boolean sessionOpening;                  /* guarded by stateLock */
 | 
| +  private CameraSession currentSession;            /* guarded by stateLock */
 | 
| +  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 */
 | 
| +  private boolean switchInProgress;                /* guarded by stateLock */
 | 
| +  private CameraSwitchHandler switchEventsHandler; /* guarded by stateLock */
 | 
| +
 | 
| +  public CameraCapturer(
 | 
| +      String cameraName, CameraEventsHandler eventsHandler, CameraEnumerator cameraEnumerator) {
 | 
| +    if (eventsHandler == null) {
 | 
| +      eventsHandler = new CameraEventsHandler() {
 | 
| +        @Override
 | 
| +        public void onCameraError(String errorDescription) {}
 | 
| +        @Override
 | 
| +        public void onCameraFreezed(String errorDescription) {}
 | 
| +        @Override
 | 
| +        public void onCameraOpening(int cameraId) {}
 | 
| +        @Override
 | 
| +        public void onFirstFrameAvailable() {}
 | 
| +        @Override
 | 
| +        public void onCameraClosed() {}
 | 
| +      };
 | 
| +    }
 | 
| +
 | 
| +    this.eventsHandler = eventsHandler;
 | 
| +    this.cameraEnumerator = cameraEnumerator;
 | 
| +    this.cameraName = cameraName;
 | 
| +
 | 
| +    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
 | 
| +  public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
 | 
| +      CapturerObserver capturerObserver) {
 | 
| +    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);
 | 
| +
 | 
| +    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;
 | 
| +      createSessionInternal(0);
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  private void createSessionInternal(int delayMs) {
 | 
| +    cameraThreadHandler.postDelayed(new Runnable() {
 | 
| +      @Override
 | 
| +      public void run() {
 | 
| +        createCameraSession(
 | 
| +            createSessionCallback,
 | 
| +            eventsHandler, applicationContext, capturerObserver, surfaceHelper,
 | 
| +            cameraName, width, height, framerate);
 | 
| +      }
 | 
| +    }, delayMs);
 | 
| +  }
 | 
| +
 | 
| +  @Override
 | 
| +  public void stopCapture() {
 | 
| +    Logging.d(TAG, "Stop capture");
 | 
| +
 | 
| +    synchronized (stateLock) {
 | 
| +      while (sessionOpening) {
 | 
| +        Logging.d(TAG, "Stop capture: Waiting for session to open");
 | 
| +        ThreadUtils.waitUninterruptibly(stateLock);
 | 
| +      }
 | 
| +
 | 
| +      if (currentSession != null) {
 | 
| +        Logging.d(TAG, "Stop capture: Stopping session");
 | 
| +        currentSession.stop();
 | 
| +        currentSession = null;
 | 
| +      } else {
 | 
| +        Logging.d(TAG, "Stop capture: No session open");
 | 
| +      }
 | 
| +    }
 | 
| +
 | 
| +    Logging.d(TAG, "Stop capture done");
 | 
| +  }
 | 
| +
 | 
| +  @Override
 | 
| +  public void onOutputFormatRequest(final int width, final int height, final int framerate) {
 | 
| +    cameraThreadHandler.post(new Runnable() {
 | 
| +      @Override public void run() {
 | 
| +        Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + height +
 | 
| +        "@" + framerate);
 | 
| +        capturerObserver.onOutputFormatRequest(width, height, framerate);
 | 
| +      }
 | 
| +    });
 | 
| +  }
 | 
| +
 | 
| +  @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");
 | 
| +
 | 
| +    final String[] deviceNames = cameraEnumerator.getDeviceNames();
 | 
| +
 | 
| +    if (deviceNames.length < 2) {
 | 
| +      if (switchEventsHandler != null) {
 | 
| +        switchEventsHandler.onCameraSwitchError("No camera to switch to.");
 | 
| +      }
 | 
| +      return;
 | 
| +    }
 | 
| +
 | 
| +    synchronized (stateLock) {
 | 
| +      if (switchInProgress) {
 | 
| +        Logging.d(TAG, "switchCamera switchInProgress");
 | 
| +        if (switchEventsHandler != null) {
 | 
| +          switchEventsHandler.onCameraSwitchError("Camera switch already in progress.");
 | 
| +        }
 | 
| +        return;
 | 
| +      }
 | 
| +
 | 
| +      if (sessionOpening) {
 | 
| +        Logging.d(TAG, "switchCamera sessionOpening");
 | 
| +        if (switchEventsHandler != null) {
 | 
| +          switchEventsHandler.onCameraSwitchError("Session is still opening.");
 | 
| +        }
 | 
| +        return;
 | 
| +      }
 | 
| +
 | 
| +      if (currentSession == null) {
 | 
| +        Logging.d(TAG, "switchCamera: No session open");
 | 
| +        if (switchEventsHandler != null) {
 | 
| +          switchEventsHandler.onCameraSwitchError("Camera is not running.");
 | 
| +        }
 | 
| +        return;
 | 
| +      }
 | 
| +
 | 
| +      Logging.d(TAG, "switchCamera: Stopping session");
 | 
| +      currentSession.stop();
 | 
| +      currentSession = null;
 | 
| +
 | 
| +      int cameraNameIndex = Arrays.asList(deviceNames).indexOf(cameraName);
 | 
| +      cameraName = deviceNames[(cameraNameIndex + 1) % deviceNames.length];
 | 
| +
 | 
| +      switchInProgress = true;
 | 
| +      this.switchEventsHandler = switchEventsHandler;
 | 
| +      sessionOpening = true;
 | 
| +      openAttemptsRemaining = 1;
 | 
| +      createSessionInternal(0);
 | 
| +    }
 | 
| +    Logging.d(TAG, "switchCamera done");
 | 
| +  }
 | 
| +
 | 
| +  protected String getCameraName() {
 | 
| +    synchronized (stateLock) {
 | 
| +      return cameraName;
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  abstract protected void createCameraSession(
 | 
| +      CameraSession.CreateSessionCallback createSessionCallback,
 | 
| +      CameraEventsHandler eventsHandler, Context applicationContext,
 | 
| +      CameraVideoCapturer.CapturerObserver capturerObserver,
 | 
| +      SurfaceTextureHelper surfaceTextureHelper,
 | 
| +      String cameraName, int width, int height, int framerate);
 | 
| +}
 | 
| 
 |