Bug 1175656 - Implement generation of recording posters in Gecko. r=dhylands,bz

This commit is contained in:
Andrew Osmond 2015-06-16 20:35:00 -04:00
Родитель ddccac2fc1
Коммит d74af6faf3
12 изменённых файлов: 356 добавлений и 18 удалений

Просмотреть файл

@ -177,6 +177,19 @@ CameraControlImpl::OnTakePictureComplete(const uint8_t* aData, uint32_t aLength,
}
}
void
CameraControlImpl::OnPoster(dom::BlobImpl* aBlobImpl)
{
// This callback can run on threads other than the Main Thread and
// the Camera Thread.
RwLockAutoEnterRead lock(mListenerLock);
for (uint32_t i = 0; i < mListeners.Length(); ++i) {
CameraControlListener* l = mListeners[i];
l->OnPoster(aBlobImpl);
}
}
void
CameraControlImpl::OnShutter()
{

Просмотреть файл

@ -20,6 +20,10 @@
namespace mozilla {
namespace dom {
class BlobImpl;
}
namespace layers {
class Image;
}
@ -57,6 +61,7 @@ protected:
void OnAutoFocusComplete(bool aAutoFocusSucceeded);
void OnFacesDetected(const nsTArray<Face>& aFaces);
void OnTakePictureComplete(const uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
void OnPoster(dom::BlobImpl* aBlobImpl);
void OnRateLimitPreview(bool aLimit);
bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);

Просмотреть файл

@ -10,6 +10,10 @@
namespace mozilla {
namespace dom {
class BlobImpl;
}
namespace layers {
class Image;
}
@ -60,6 +64,8 @@ public:
{
kRecorderStopped,
kRecorderStarted,
kPosterCreated,
kPosterFailed,
#ifdef MOZ_B2G_CAMERA
kFileSizeLimitReached,
kVideoLengthLimitReached,
@ -91,6 +97,7 @@ public:
virtual void OnAutoFocusMoving(bool aIsMoving) { }
virtual void OnTakePictureComplete(const uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) { }
virtual void OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces) { }
virtual void OnPoster(dom::BlobImpl* aBlobImpl) { }
enum UserContext
{

Просмотреть файл

@ -82,6 +82,19 @@ nsDOMCameraControl::HasSupport(JSContext* aCx, JSObject* aGlobal)
return Navigator::HasCameraSupport(aCx, aGlobal);
}
static nsresult
RegisterStorageRequestEvents(DOMRequest* aRequest, nsIDOMEventListener* aListener)
{
EventListenerManager* elm = aRequest->GetOrCreateListenerManager();
if (NS_WARN_IF(!elm)) {
return NS_ERROR_UNEXPECTED;
}
elm->AddEventListener(NS_LITERAL_STRING("success"), aListener, false, false);
elm->AddEventListener(NS_LITERAL_STRING("error"), aListener, false, false);
return NS_OK;
}
class mozilla::StartRecordingHelper : public nsIDOMEventListener
{
public:
@ -90,6 +103,7 @@ public:
explicit StartRecordingHelper(nsDOMCameraControl* aDOMCameraControl)
: mDOMCameraControl(aDOMCameraControl)
, mState(false)
{
MOZ_COUNT_CTOR(StartRecordingHelper);
}
@ -98,10 +112,12 @@ protected:
virtual ~StartRecordingHelper()
{
MOZ_COUNT_DTOR(StartRecordingHelper);
mDOMCameraControl->OnCreatedFileDescriptor(mState);
}
protected:
nsRefPtr<nsDOMCameraControl> mDOMCameraControl;
bool mState;
};
NS_IMETHODIMP
@ -109,13 +125,50 @@ StartRecordingHelper::HandleEvent(nsIDOMEvent* aEvent)
{
nsString eventType;
aEvent->GetType(eventType);
mDOMCameraControl->OnCreatedFileDescriptor(eventType.EqualsLiteral("success"));
mState = eventType.EqualsLiteral("success");
return NS_OK;
}
NS_IMPL_ISUPPORTS(mozilla::StartRecordingHelper, nsIDOMEventListener)
class mozilla::RecorderPosterHelper : public nsIDOMEventListener
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
explicit RecorderPosterHelper(nsDOMCameraControl* aDOMCameraControl)
: mDOMCameraControl(aDOMCameraControl)
, mState(CameraControlListener::kPosterFailed)
{
MOZ_COUNT_CTOR(RecorderPosterHelper);
}
protected:
virtual ~RecorderPosterHelper()
{
MOZ_COUNT_DTOR(RecorderPosterHelper);
mDOMCameraControl->OnRecorderStateChange(mState, 0, 0);
}
protected:
nsRefPtr<nsDOMCameraControl> mDOMCameraControl;
CameraControlListener::RecorderState mState;
};
NS_IMETHODIMP
RecorderPosterHelper::HandleEvent(nsIDOMEvent* aEvent)
{
nsString eventType;
aEvent->GetType(eventType);
if (eventType.EqualsLiteral("success")) {
mState = CameraControlListener::kPosterCreated;
}
return NS_OK;
}
NS_IMPL_ISUPPORTS(mozilla::RecorderPosterHelper, nsIDOMEventListener)
nsDOMCameraControl::DOMCameraConfiguration::DOMCameraConfiguration()
: CameraConfiguration()
, mMaxFocusAreas(0)
@ -202,6 +255,7 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
, mWindow(aWindow)
, mPreviewState(CameraControlListener::kPreviewStopped)
, mRecording(false)
, mRecordingStoppedDeferred(false)
, mSetInitialConfig(false)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
@ -743,7 +797,18 @@ nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
return nullptr;
}
if (mStartRecordingPromise || mRecording) {
// Must supply both the poster path and storage area or neither
if (aOptions.mPosterFilepath.IsEmpty() ==
static_cast<bool>(aOptions.mPosterStorageArea.get())) {
promise->MaybeReject(NS_ERROR_ILLEGAL_VALUE);
return promise.forget();
}
// If we are trying to start recording, already recording or are still
// waiting for a poster to be created/fail, we need to wait
if (mStartRecordingPromise || mRecording ||
mRecordingStoppedDeferred ||
!mOptions.mPosterFilepath.IsEmpty()) {
promise->MaybeReject(NS_ERROR_IN_PROGRESS);
return promise.forget();
}
@ -762,20 +827,16 @@ nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
return nullptr;
}
mStartRecordingPromise = promise;
mOptions = aOptions;
EventListenerManager* elm = request->GetOrCreateListenerManager();
if (!elm) {
nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
aRv = RegisterStorageRequestEvents(request, listener);
if (aRv.Failed()) {
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
mStartRecordingPromise = promise;
mOptions = aOptions;
mRecording = true;
nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
elm->AddEventListener(NS_LITERAL_STRING("success"), listener, false, false);
elm->AddEventListener(NS_LITERAL_STRING("error"), listener, false, false);
return promise.forget();
}
@ -790,6 +851,8 @@ nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded)
// Race condition where StopRecording comes in before we issue
// the start recording request to Gonk
rv = NS_ERROR_ABORT;
mOptions.mPosterFilepath.Truncate();
mOptions.mPosterStorageArea = nullptr;
} else if (aSucceeded && mDSFileDescriptor->mFileDescriptor.IsValid()) {
ICameraControl::StartRecordingOptions o;
@ -797,6 +860,7 @@ nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded)
o.maxFileSizeBytes = mOptions.mMaxFileSizeBytes;
o.maxVideoLengthMs = mOptions.mMaxVideoLengthMs;
o.autoEnableLowLightTorch = mOptions.mAutoEnableLowLightTorch;
o.createPoster = !mOptions.mPosterFilepath.IsEmpty();
rv = mCameraControl->StartRecording(mDSFileDescriptor.get(), &o);
if (NS_SUCCEEDED(rv)) {
return;
@ -1254,6 +1318,38 @@ nsDOMCameraControl::OnPreviewStateChange(CameraControlListener::PreviewState aSt
DispatchPreviewStateEvent(aState);
}
void
nsDOMCameraControl::OnPoster(BlobImpl* aPoster)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mOptions.mPosterFilepath.IsEmpty());
// Destructor will trigger an error notification if any step fails
nsRefPtr<RecorderPosterHelper> listener = new RecorderPosterHelper(this);
if (NS_WARN_IF(!aPoster)) {
return;
}
nsRefPtr<Blob> blob = Blob::Create(GetParentObject(), aPoster);
if (NS_WARN_IF(!blob)) {
return;
}
if (NS_WARN_IF(!mOptions.mPosterStorageArea)) {
return;
}
ErrorResult rv;
nsRefPtr<DOMRequest> request =
mOptions.mPosterStorageArea->AddNamed(blob, mOptions.mPosterFilepath, rv);
if (NS_WARN_IF(rv.Failed())) {
return;
}
RegisterStorageRequestEvents(request, listener);
}
void
nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState aState,
int32_t aArg, int32_t aTrackNum)
@ -1278,10 +1374,27 @@ nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState a
break;
case CameraControlListener::kRecorderStopped:
if (!mOptions.mPosterFilepath.IsEmpty()) {
mRecordingStoppedDeferred = true;
return;
}
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
state = NS_LITERAL_STRING("Stopped");
break;
case CameraControlListener::kPosterCreated:
state = NS_LITERAL_STRING("PosterCreated");
mOptions.mPosterFilepath.Truncate();
mOptions.mPosterStorageArea = nullptr;
break;
case CameraControlListener::kPosterFailed:
state = NS_LITERAL_STRING("PosterFailed");
mOptions.mPosterFilepath.Truncate();
mOptions.mPosterStorageArea = nullptr;
break;
#ifdef MOZ_B2G_CAMERA
case CameraControlListener::kFileSizeLimitReached:
state = NS_LITERAL_STRING("FileSizeLimitReached");
@ -1314,6 +1427,11 @@ nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState a
}
DispatchStateEvent(NS_LITERAL_STRING("recorderstatechange"), state);
if (mRecordingStoppedDeferred && mOptions.mPosterFilepath.IsEmpty()) {
mRecordingStoppedDeferred = false;
OnRecorderStateChange(CameraControlListener::kRecorderStopped, 0, 0);
}
}
void

Просмотреть файл

@ -39,6 +39,7 @@ namespace dom {
}
class ErrorResult;
class StartRecordingHelper;
class RecorderPosterHelper;
#define NS_DOM_CAMERA_CONTROL_CID \
{ 0x3700c096, 0xf920, 0x438d, \
@ -163,6 +164,7 @@ protected:
friend class DOMCameraControlListener;
friend class mozilla::StartRecordingHelper;
friend class mozilla::RecorderPosterHelper;
void OnCreatedFileDescriptor(bool aSucceeded);
@ -170,6 +172,7 @@ protected:
void OnAutoFocusMoving(bool aIsMoving);
void OnTakePictureComplete(nsIDOMBlob* aPicture);
void OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces);
void OnPoster(dom::BlobImpl* aPoster);
void OnGetCameraComplete();
void OnHardwareStateChange(DOMCameraControlListener::HardwareState aState, nsresult aReason);
@ -225,6 +228,7 @@ protected:
nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
DOMCameraControlListener::PreviewState mPreviewState;
bool mRecording;
bool mRecordingStoppedDeferred;
bool mSetInitialConfig;
#ifdef MOZ_WIDGET_GONK

Просмотреть файл

@ -410,3 +410,27 @@ DOMCameraControlListener::OnUserError(UserContext aContext, nsresult aError)
NS_DispatchToMainThread(new Callback(mDOMCameraControl, aContext, aError));
}
void
DOMCameraControlListener::OnPoster(BlobImpl* aBlobImpl)
{
class Callback : public DOMCallback
{
public:
Callback(nsMainThreadPtrHandle<nsISupports> aDOMCameraControl, BlobImpl* aBlobImpl)
: DOMCallback(aDOMCameraControl)
, mBlobImpl(aBlobImpl)
{ }
void
RunCallback(nsDOMCameraControl* aDOMCameraControl) override
{
aDOMCameraControl->OnPoster(mBlobImpl);
}
protected:
nsRefPtr<BlobImpl> mBlobImpl;
};
NS_DispatchToMainThread(new Callback(mDOMCameraControl, aBlobImpl));
}

Просмотреть файл

@ -31,6 +31,7 @@ public:
virtual void OnRateLimitPreview(bool aLimit) override;
virtual bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) override;
virtual void OnUserError(UserContext aContext, nsresult aError) override;
virtual void OnPoster(dom::BlobImpl* aBlobImpl) override;
protected:
virtual ~DOMCameraControlListener();

Просмотреть файл

@ -27,7 +27,12 @@
#include <media/mediaplayer.h>
#include <media/MediaProfiles.h>
#include "GrallocImages.h"
#include "imgIEncoder.h"
#include "libyuv.h"
#include "nsNetUtil.h" // for NS_ReadInputStreamToBuffer
#endif
#include "nsNetCID.h" // for NS_STREAMTRANSPORTSERVICE_CONTRACTID
#include "nsAutoPtr.h" // for nsAutoArrayPtr
#include "nsCOMPtr.h"
#include "nsMemory.h"
#include "nsThread.h"
@ -38,7 +43,6 @@
#include "mozilla/ipc/FileDescriptorUtils.h"
#include "nsAlgorithm.h"
#include "nsPrintfCString.h"
#include "AutoRwLock.h"
#include "GonkCameraHwMgr.h"
#include "GonkRecorderProfiles.h"
#include "CameraCommon.h"
@ -46,6 +50,7 @@
#include "DeviceStorageFileDescriptor.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::layers;
using namespace mozilla::gfx;
using namespace mozilla::ipc;
@ -79,6 +84,7 @@ nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId)
#endif
, mRecorderMonitor("GonkCameraControl::mRecorder.Monitor")
, mVideoFile(nullptr)
, mCapturePoster(false)
, mAutoFocusPending(false)
, mAutoFocusCompleteExpired(0)
, mReentrantMonitor("GonkCameraControl::OnTakePicture.Monitor")
@ -1252,6 +1258,7 @@ nsGonkCameraControl::StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescri
#endif
OnRecorderStateChange(CameraControlListener::kRecorderStarted);
mCapturePoster = aOptions->createPoster;
return NS_OK;
}
@ -1296,6 +1303,9 @@ nsGonkCameraControl::StopRecordingImpl()
return NS_OK;
}
#endif
if (mCapturePoster.exchange(false)) {
OnPoster(nullptr, 0);
}
OnRecorderStateChange(CameraControlListener::kRecorderStopped);
{
@ -2018,11 +2028,13 @@ nsGonkCameraControl::SetupRecording(int aFd, int aRotation,
NS_ERROR_INVALID_ARG);
// adjust rotation by camera sensor offset
int r = aRotation;
r += mCameraHw->GetSensorOrientation();
r = RationalizeRotation(r);
DOM_CAMERA_LOGI("setting video rotation to %d degrees (mapped from %d)\n", r, aRotation);
snprintf(buffer, SIZE, "video-param-rotation-angle-degrees=%d", r);
mVideoRotation = aRotation;
mVideoRotation += mCameraHw->GetSensorOrientation();
mVideoRotation = RationalizeRotation(mVideoRotation);
DOM_CAMERA_LOGI("setting video rotation to %d degrees (mapped from %d)\n",
mVideoRotation, aRotation);
snprintf(buffer, SIZE, "video-param-rotation-angle-degrees=%d",
mVideoRotation);
CHECK_SETARG_RETURN(mRecorder->setParameters(String8(buffer)),
NS_ERROR_INVALID_ARG);
@ -2165,6 +2177,137 @@ nsGonkCameraControl::OnRateLimitPreview(bool aLimit)
CameraControlImpl::OnRateLimitPreview(aLimit);
}
void
nsGonkCameraControl::CreatePoster(Image* aImage, uint32_t aWidth, uint32_t aHeight, int32_t aRotation)
{
class PosterRunnable : public nsRunnable {
public:
PosterRunnable(nsGonkCameraControl* aTarget, Image* aImage,
uint32_t aWidth, uint32_t aHeight, int32_t aRotation)
: mTarget(aTarget)
, mImage(aImage)
, mWidth(aWidth)
, mHeight(aHeight)
, mRotation(aRotation)
, mDst(nullptr)
, mDstLength(0)
{ }
virtual ~PosterRunnable()
{
mTarget->OnPoster(mDst, mDstLength);
}
NS_IMETHODIMP Run() override
{
#ifdef MOZ_WIDGET_GONK
// NV21 (yuv420sp) is 12 bits / pixel
size_t srcLength = (mWidth * mHeight * 3 + 1) / 2;
// ARGB is 32 bits / pixel
size_t tmpLength = mWidth * mHeight * sizeof(uint32_t);
nsAutoArrayPtr<uint8_t> tmp;
tmp = new uint8_t[tmpLength];
GrallocImage* nativeImage = static_cast<GrallocImage*>(mImage.get());
android::sp<GraphicBuffer> graphicBuffer = nativeImage->GetGraphicBuffer();
void* graphicSrc = nullptr;
graphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_MASK, &graphicSrc);
uint32_t stride = mWidth * 4;
int err = libyuv::ConvertToARGB(static_cast<uint8_t*>(graphicSrc),
srcLength, tmp, stride, 0, 0,
mWidth, mHeight, mWidth, mHeight,
libyuv::kRotate0, libyuv::FOURCC_NV21);
graphicBuffer->unlock();
graphicSrc = nullptr;
graphicBuffer.clear();
nativeImage = nullptr;
mImage = nullptr;
if (NS_WARN_IF(err < 0)) {
DOM_CAMERA_LOGE("CreatePoster: to ARGB failed (%d)\n", err);
return NS_OK;
}
nsCOMPtr<imgIEncoder> encoder =
do_CreateInstance("@mozilla.org/image/encoder;2?type=image/jpeg");
if (NS_WARN_IF(!encoder)) {
DOM_CAMERA_LOGE("CreatePoster: no JPEG encoder\n");
return NS_OK;
}
nsString opt;
nsresult rv = encoder->InitFromData(tmp, tmpLength, mWidth,
mHeight, stride,
imgIEncoder::INPUT_FORMAT_HOSTARGB,
opt);
if (NS_WARN_IF(NS_FAILED(rv))) {
DOM_CAMERA_LOGE("CreatePoster: encoder init failed (0x%x)\n",
rv);
return NS_OK;
}
nsCOMPtr<nsIInputStream> stream = do_QueryInterface(encoder);
if (NS_WARN_IF(!stream)) {
DOM_CAMERA_LOGE("CreatePoster: to input stream failed\n");
return NS_OK;
}
uint64_t length = 0;
rv = stream->Available(&length);
if (NS_WARN_IF(NS_FAILED(rv))) {
DOM_CAMERA_LOGE("CreatePoster: get length failed (0x%x)\n",
rv);
return NS_OK;
}
rv = NS_ReadInputStreamToBuffer(stream, &mDst, length);
if (NS_WARN_IF(NS_FAILED(rv))) {
DOM_CAMERA_LOGE("CreatePoster: read failed (0x%x)\n", rv);
mDst = nullptr;
return NS_OK;
}
mDstLength = length;
#endif
return NS_OK;
}
private:
nsRefPtr<nsGonkCameraControl> mTarget;
nsRefPtr<Image> mImage;
int32_t mWidth;
int32_t mHeight;
int32_t mRotation;
void* mDst;
size_t mDstLength;
};
nsCOMPtr<nsIRunnable> event = new PosterRunnable(this, aImage,
aWidth,
aHeight,
aRotation);
nsCOMPtr<nsIEventTarget> target
= do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
MOZ_ASSERT(target);
target->Dispatch(event, NS_DISPATCH_NORMAL);
}
void
nsGonkCameraControl::OnPoster(void* aData, uint32_t aLength)
{
nsRefPtr<BlobImpl> blobImpl;
if (aData) {
blobImpl = new BlobImplMemory(aData, aLength, NS_LITERAL_STRING("image/jpeg"));
}
CameraControlImpl::OnPoster(blobImpl);
}
void
nsGonkCameraControl::OnNewPreviewFrame(layers::TextureClient* aBuffer)
{
@ -2179,6 +2322,14 @@ nsGonkCameraControl::OnNewPreviewFrame(layers::TextureClient* aBuffer)
mCurrentConfiguration.mPreviewSize.height);
videoImage->SetData(data);
if (mCapturePoster.exchange(false)) {
CreatePoster(frame,
mCurrentConfiguration.mPreviewSize.width,
mCurrentConfiguration.mPreviewSize.height,
mVideoRotation);
return;
}
OnNewPreviewFrame(frame, mCurrentConfiguration.mPreviewSize.width,
mCurrentConfiguration.mPreviewSize.height);
#endif

Просмотреть файл

@ -47,6 +47,7 @@ namespace mozilla {
namespace layers {
class TextureClient;
class ImageContainer;
class Image;
}
class nsGonkCameraControl : public CameraControlImpl
@ -60,6 +61,7 @@ public:
void OnTakePictureComplete(uint8_t* aData, uint32_t aLength);
void OnTakePictureError();
void OnRateLimitPreview(bool aLimit);
void OnPoster(void* aData, uint32_t aLength);
void OnNewPreviewFrame(layers::TextureClient* aBuffer);
#ifdef MOZ_WIDGET_GONK
void OnRecorderEvent(int msg, int ext1, int ext2);
@ -149,6 +151,8 @@ protected:
nsresult PausePreview();
nsresult GetSupportedSize(const Size& aSize, const nsTArray<Size>& supportedSizes, Size& best);
void CreatePoster(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight, int32_t aRotation);
nsresult LoadRecorderProfiles();
static PLDHashOperator Enumerate(const nsAString& aProfileName,
RecorderProfile* aProfile,
@ -198,6 +202,9 @@ protected:
nsRefPtr<DeviceStorageFile> mVideoFile;
nsString mFileFormat;
Atomic<bool> mCapturePoster;
int32_t mVideoRotation;
bool mAutoFocusPending;
nsCOMPtr<nsITimer> mAutoFocusCompleteTimer;
int32_t mAutoFocusCompleteExpired;

Просмотреть файл

@ -138,6 +138,7 @@ public:
uint64_t maxFileSizeBytes;
uint64_t maxVideoLengthMs;
bool autoEnableLowLightTorch;
bool createPoster;
};
struct Configuration {

Просмотреть файл

@ -65,6 +65,7 @@ FAIL_ON_WARNINGS = True
LOCAL_INCLUDES += [
'../base',
'/media/libyuv/include',
]
include('/ipc/chromium/chromium-config.mozbuild')

Просмотреть файл

@ -109,6 +109,12 @@ dictionary CameraStartRecordingOptions
will be left as-is on stopRecording(). If the camera does not
support this setting, it will be ignored. */
boolean autoEnableLowLightTorch = false;
/* If given, a poster JPG will be created from the recording and saved
at the given path. PosterCreated or PosterFailed recording state
changes will indicate whether or not it was created. */
DOMString posterFilepath = "";
DeviceStorage? posterStorageArea = null;
};
/*