gecko-dev/dom/camera/DOMCameraControl.cpp

649 строки
19 KiB
C++

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "base/basictypes.h"
#include "nsCOMPtr.h"
#include "nsDOMClassInfo.h"
#include "nsHashPropertyBag.h"
#include "nsThread.h"
#include "DeviceStorage.h"
#include "mozilla/dom/CameraControlBinding.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/Services.h"
#include "mozilla/unused.h"
#include "nsIAppsService.h"
#include "nsIObserverService.h"
#include "nsIDOMDeviceStorage.h"
#include "nsIScriptSecurityManager.h"
#include "nsXULAppAPI.h"
#include "DOMCameraManager.h"
#include "DOMCameraCapabilities.h"
#include "DOMCameraControl.h"
#include "CameraCommon.h"
#include "mozilla/dom/CameraManagerBinding.h"
#include "mozilla/dom/BindingUtils.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::idl;
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(nsDOMCameraControl, mDOMCapabilities, mWindow)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCameraControl)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCameraControl)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCameraControl)
nsDOMCameraControl::~nsDOMCameraControl()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
JSObject*
nsDOMCameraControl::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
{
return CameraControlBinding::Wrap(aCx, aScope, this);
}
nsICameraCapabilities*
nsDOMCameraControl::Capabilities()
{
if (!mDOMCapabilities) {
mDOMCapabilities = new DOMCameraCapabilities(mCameraControl);
}
return mDOMCapabilities;
}
void
nsDOMCameraControl::GetEffect(nsString& aEffect, ErrorResult& aRv)
{
aRv = mCameraControl->Get(CAMERA_PARAM_EFFECT, aEffect);
}
void
nsDOMCameraControl::SetEffect(const nsAString& aEffect, ErrorResult& aRv)
{
aRv = mCameraControl->Set(CAMERA_PARAM_EFFECT, aEffect);
}
void
nsDOMCameraControl::GetWhiteBalanceMode(nsString& aWhiteBalanceMode, ErrorResult& aRv)
{
aRv = mCameraControl->Get(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
}
void
nsDOMCameraControl::SetWhiteBalanceMode(const nsAString& aWhiteBalanceMode, ErrorResult& aRv)
{
aRv = mCameraControl->Set(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
}
void
nsDOMCameraControl::GetSceneMode(nsString& aSceneMode, ErrorResult& aRv)
{
aRv = mCameraControl->Get(CAMERA_PARAM_SCENEMODE, aSceneMode);
}
void
nsDOMCameraControl::SetSceneMode(const nsAString& aSceneMode, ErrorResult& aRv)
{
aRv = mCameraControl->Set(CAMERA_PARAM_SCENEMODE, aSceneMode);
}
void
nsDOMCameraControl::GetFlashMode(nsString& aFlashMode, ErrorResult& aRv)
{
aRv = mCameraControl->Get(CAMERA_PARAM_FLASHMODE, aFlashMode);
}
void
nsDOMCameraControl::SetFlashMode(const nsAString& aFlashMode, ErrorResult& aRv)
{
aRv = mCameraControl->Set(CAMERA_PARAM_FLASHMODE, aFlashMode);
}
void
nsDOMCameraControl::GetFocusMode(nsString& aFocusMode, ErrorResult& aRv)
{
aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSMODE, aFocusMode);
}
void
nsDOMCameraControl::SetFocusMode(const nsAString& aFocusMode, ErrorResult& aRv)
{
aRv = mCameraControl->Set(CAMERA_PARAM_FOCUSMODE, aFocusMode);
}
double
nsDOMCameraControl::GetZoom(ErrorResult& aRv)
{
double zoom;
aRv = mCameraControl->Get(CAMERA_PARAM_ZOOM, &zoom);
return zoom;
}
void
nsDOMCameraControl::SetZoom(double aZoom, ErrorResult& aRv)
{
aRv = mCameraControl->Set(CAMERA_PARAM_ZOOM, aZoom);
}
/* attribute jsval meteringAreas; */
JS::Value
nsDOMCameraControl::GetMeteringAreas(JSContext* cx, ErrorResult& aRv)
{
JS::Rooted<JS::Value> areas(cx);
aRv = mCameraControl->Get(cx, CAMERA_PARAM_METERINGAREAS, areas.address());
return areas;
}
void
nsDOMCameraControl::SetMeteringAreas(JSContext* cx, JS::Handle<JS::Value> aMeteringAreas, ErrorResult& aRv)
{
aRv = mCameraControl->SetMeteringAreas(cx, aMeteringAreas);
}
JS::Value
nsDOMCameraControl::GetFocusAreas(JSContext* cx, ErrorResult& aRv)
{
JS::Rooted<JS::Value> value(cx);
aRv = mCameraControl->Get(cx, CAMERA_PARAM_FOCUSAREAS, value.address());
return value;
}
void
nsDOMCameraControl::SetFocusAreas(JSContext* cx, JS::Handle<JS::Value> aFocusAreas, ErrorResult& aRv)
{
aRv = mCameraControl->SetFocusAreas(cx, aFocusAreas);
}
static nsresult
GetSize(JSContext* aCx, JS::Value* aValue, const CameraSize& aSize)
{
JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, nullptr, nullptr));
if (!o) {
return NS_ERROR_OUT_OF_MEMORY;
}
JS::Rooted<JS::Value> v(aCx);
v = INT_TO_JSVAL(aSize.width);
if (!JS_SetProperty(aCx, o, "width", v)) {
return NS_ERROR_FAILURE;
}
v = INT_TO_JSVAL(aSize.height);
if (!JS_SetProperty(aCx, o, "height", v)) {
return NS_ERROR_FAILURE;
}
*aValue = JS::ObjectValue(*o);
return NS_OK;
}
/* attribute any pictureSize */
JS::Value
nsDOMCameraControl::GetPictureSize(JSContext* cx, ErrorResult& aRv)
{
JS::Rooted<JS::Value> value(cx);
CameraSize size;
aRv = mCameraControl->Get(CAMERA_PARAM_PICTURESIZE, size);
if (aRv.Failed()) {
return value;
}
aRv = GetSize(cx, value.address(), size);
return value;
}
void
nsDOMCameraControl::SetPictureSize(JSContext* cx, JS::Handle<JS::Value> aSize, ErrorResult& aRv)
{
CameraSize size;
aRv = size.Init(cx, aSize.address());
if (aRv.Failed()) {
return;
}
aRv = mCameraControl->Set(CAMERA_PARAM_PICTURESIZE, size);
}
/* attribute any thumbnailSize */
JS::Value
nsDOMCameraControl::GetThumbnailSize(JSContext* cx, ErrorResult& aRv)
{
JS::Rooted<JS::Value> value(cx);
CameraSize size;
aRv = mCameraControl->Get(CAMERA_PARAM_THUMBNAILSIZE, size);
if (aRv.Failed()) {
return value;
}
aRv = GetSize(cx, value.address(), size);
return value;
}
void
nsDOMCameraControl::SetThumbnailSize(JSContext* cx, JS::Handle<JS::Value> aSize, ErrorResult& aRv)
{
CameraSize size;
aRv = size.Init(cx, aSize.address());
if (aRv.Failed()) {
return;
}
aRv = mCameraControl->Set(CAMERA_PARAM_THUMBNAILSIZE, size);
}
double
nsDOMCameraControl::GetFocalLength(ErrorResult& aRv)
{
double focalLength;
aRv = mCameraControl->Get(CAMERA_PARAM_FOCALLENGTH, &focalLength);
return focalLength;
}
double
nsDOMCameraControl::GetFocusDistanceNear(ErrorResult& aRv)
{
double distance;
aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCENEAR, &distance);
return distance;
}
double
nsDOMCameraControl::GetFocusDistanceOptimum(ErrorResult& aRv)
{
double distance;
aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, &distance);
return distance;
}
double
nsDOMCameraControl::GetFocusDistanceFar(ErrorResult& aRv)
{
double distance;
aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEFAR, &distance);
return distance;
}
void
nsDOMCameraControl::SetExposureCompensation(const Optional<double>& aCompensation, ErrorResult& aRv)
{
if (!aCompensation.WasPassed()) {
// use NaN to switch the camera back into auto mode
aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, NAN);
}
aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, aCompensation.Value());
}
double
nsDOMCameraControl::GetExposureCompensation(ErrorResult& aRv)
{
double compensation;
aRv = mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, &compensation);
return compensation;
}
already_AddRefed<nsICameraShutterCallback>
nsDOMCameraControl::GetOnShutter(ErrorResult& aRv)
{
nsCOMPtr<nsICameraShutterCallback> cb;
aRv = mCameraControl->Get(getter_AddRefs(cb));
return cb.forget();
}
void
nsDOMCameraControl::SetOnShutter(nsICameraShutterCallback* aOnShutter,
ErrorResult& aRv)
{
aRv = mCameraControl->Set(aOnShutter);
}
/* attribute nsICameraClosedCallback onClosed; */
already_AddRefed<nsICameraClosedCallback>
nsDOMCameraControl::GetOnClosed(ErrorResult& aRv)
{
nsCOMPtr<nsICameraClosedCallback> onClosed;
aRv = mCameraControl->Get(getter_AddRefs(onClosed));
return onClosed.forget();
}
void
nsDOMCameraControl::SetOnClosed(nsICameraClosedCallback* aOnClosed,
ErrorResult& aRv)
{
aRv = mCameraControl->Set(aOnClosed);
}
already_AddRefed<nsICameraRecorderStateChange>
nsDOMCameraControl::GetOnRecorderStateChange(ErrorResult& aRv)
{
nsCOMPtr<nsICameraRecorderStateChange> cb;
aRv = mCameraControl->Get(getter_AddRefs(cb));
return cb.forget();
}
void
nsDOMCameraControl::SetOnRecorderStateChange(nsICameraRecorderStateChange* aOnRecorderStateChange,
ErrorResult& aRv)
{
aRv = mCameraControl->Set(aOnRecorderStateChange);
}
void
nsDOMCameraControl::StartRecording(JSContext* aCx,
JS::Handle<JS::Value> aOptions,
nsDOMDeviceStorage& storageArea,
const nsAString& filename,
nsICameraStartRecordingCallback* onSuccess,
const Optional<nsICameraErrorCallback*>& onError,
ErrorResult& aRv)
{
MOZ_ASSERT(onSuccess, "no onSuccess handler passed");
mozilla::idl::CameraStartRecordingOptions options;
// Default values, until the dictionary parser can handle them.
options.rotation = 0;
options.maxFileSizeBytes = 0;
options.maxVideoLengthMs = 0;
aRv = options.Init(aCx, aOptions.address());
if (aRv.Failed()) {
return;
}
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (!obs) {
NS_WARNING("Could not get the Observer service for CameraControl::StartRecording.");
aRv.Throw(NS_ERROR_FAILURE);
return;
}
nsRefPtr<nsHashPropertyBag> props = CreateRecordingDeviceEventsSubject();
obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
"recording-device-events",
NS_LITERAL_STRING("starting").get());
// Forward recording events to parent process.
// The events are gathered in chrome process and used for recording indicator
if (XRE_GetProcessType() != GeckoProcessType_Default) {
unused << TabChild::GetFrom(mWindow)->SendRecordingDeviceEvents(NS_LITERAL_STRING("starting"),
true /* isAudio */,
true /* isVideo */);
}
#ifdef MOZ_B2G
if (!mAudioChannelAgent) {
mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
if (mAudioChannelAgent) {
// Camera app will stop recording when it falls to the background, so no callback is necessary.
mAudioChannelAgent->Init(AUDIO_CHANNEL_CONTENT, nullptr);
// Video recording doesn't output any sound, so it's not necessary to check canPlay.
int32_t canPlay;
mAudioChannelAgent->StartPlaying(&canPlay);
}
}
#endif
nsCOMPtr<nsIFile> folder;
aRv = storageArea.GetRootDirectoryForFile(filename, getter_AddRefs(folder));
if (aRv.Failed()) {
return;
}
aRv = mCameraControl->StartRecording(&options, folder, filename, onSuccess,
onError.WasPassed() ? onError.Value() : nullptr);
}
void
nsDOMCameraControl::StopRecording(ErrorResult& aRv)
{
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (!obs) {
NS_WARNING("Could not get the Observer service for CameraControl::StopRecording.");
aRv.Throw(NS_ERROR_FAILURE);
}
nsRefPtr<nsHashPropertyBag> props = CreateRecordingDeviceEventsSubject();
obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props) ,
"recording-device-events",
NS_LITERAL_STRING("shutdown").get());
// Forward recording events to parent process.
// The events are gathered in chrome process and used for recording indicator
if (XRE_GetProcessType() != GeckoProcessType_Default) {
unused << TabChild::GetFrom(mWindow)->SendRecordingDeviceEvents(NS_LITERAL_STRING("shutdown"),
true /* isAudio */,
true /* isVideo */);
}
#ifdef MOZ_B2G
if (mAudioChannelAgent) {
mAudioChannelAgent->StopPlaying();
mAudioChannelAgent = nullptr;
}
#endif
aRv = mCameraControl->StopRecording();
}
void
nsDOMCameraControl::GetPreviewStream(JSContext* aCx,
JS::Handle<JS::Value> aOptions,
nsICameraPreviewStreamCallback* onSuccess,
const Optional<nsICameraErrorCallback*>& onError,
ErrorResult& aRv)
{
mozilla::idl::CameraSize size;
aRv = size.Init(aCx, aOptions.address());
if (aRv.Failed()) {
return;
}
aRv = mCameraControl->GetPreviewStream(size, onSuccess,
onError.WasPassed()
? onError.Value() : nullptr);
}
void
nsDOMCameraControl::ResumePreview(ErrorResult& aRv)
{
aRv = mCameraControl->StartPreview(nullptr);
}
already_AddRefed<nsICameraPreviewStateChange>
nsDOMCameraControl::GetOnPreviewStateChange() const
{
nsCOMPtr<nsICameraPreviewStateChange> cb;
mCameraControl->Get(getter_AddRefs(cb));
return cb.forget();
}
void
nsDOMCameraControl::SetOnPreviewStateChange(nsICameraPreviewStateChange* aCb)
{
mCameraControl->Set(aCb);
}
void
nsDOMCameraControl::AutoFocus(nsICameraAutoFocusCallback* onSuccess,
const Optional<nsICameraErrorCallback*>& onError,
ErrorResult& aRv)
{
aRv = mCameraControl->AutoFocus(onSuccess,
onError.WasPassed() ? onError.Value() : nullptr);
}
void
nsDOMCameraControl::TakePicture(JSContext* aCx,
const CameraPictureOptions& aOptions,
nsICameraTakePictureCallback* onSuccess,
const Optional<nsICameraErrorCallback*>& aOnError,
ErrorResult& aRv)
{
mozilla::idl::CameraSize size;
mozilla::idl::CameraPosition pos;
aRv = size.Init(aCx, &aOptions.mPictureSize);
if (aRv.Failed()) {
return;
}
/**
* Default values, until the dictionary parser can handle them.
* NaN indicates no value provided.
*/
pos.latitude = NAN;
pos.longitude = NAN;
pos.altitude = NAN;
pos.timestamp = NAN;
aRv = pos.Init(aCx, &aOptions.mPosition);
if (aRv.Failed()) {
return;
}
nsICameraErrorCallback* onError =
aOnError.WasPassed() ? aOnError.Value() : nullptr;
aRv = mCameraControl->TakePicture(size, aOptions.mRotation,
aOptions.mFileFormat, pos,
aOptions.mDateTime, onSuccess, onError);
}
void
nsDOMCameraControl::GetPreviewStreamVideoMode(JSContext* aCx,
JS::Handle<JS::Value> aOptions,
nsICameraPreviewStreamCallback* onSuccess,
const Optional<nsICameraErrorCallback*>& onError,
ErrorResult& aRv)
{
mozilla::idl::CameraRecorderOptions options;
aRv = options.Init(aCx, aOptions.address());
if (aRv.Failed()) {
return;
}
aRv = mCameraControl->GetPreviewStreamVideoMode(&options, onSuccess,
onError.WasPassed()
? onError.Value() : nullptr);
}
void
nsDOMCameraControl::ReleaseHardware(const Optional<nsICameraReleaseCallback*>& onSuccess,
const Optional<nsICameraErrorCallback*>& onError,
ErrorResult& aRv)
{
aRv =
mCameraControl->ReleaseHardware(
onSuccess.WasPassed() ? onSuccess.Value() : nullptr,
onError.WasPassed() ? onError.Value() : nullptr);
}
class GetCameraResult : public nsRunnable
{
public:
GetCameraResult(nsDOMCameraControl* aDOMCameraControl,
nsresult aResult,
const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess,
const nsMainThreadPtrHandle<nsICameraErrorCallback>& onError,
uint64_t aWindowId)
: mDOMCameraControl(aDOMCameraControl)
, mResult(aResult)
, mOnSuccessCb(onSuccess)
, mOnErrorCb(onError)
, mWindowId(aWindowId)
{ }
NS_IMETHOD Run()
{
MOZ_ASSERT(NS_IsMainThread());
if (nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
DOM_CAMERA_LOGT("%s : this=%p -- BEFORE CALLBACK\n", __func__, this);
if (NS_FAILED(mResult)) {
if (mOnErrorCb.get()) {
mOnErrorCb->HandleEvent(NS_LITERAL_STRING("FAILURE"));
}
} else {
if (mOnSuccessCb.get()) {
mOnSuccessCb->HandleEvent(mDOMCameraControl);
}
}
DOM_CAMERA_LOGT("%s : this=%p -- AFTER CALLBACK\n", __func__, this);
}
/**
* Finally, release the extra reference to the DOM-facing camera control.
* See the nsDOMCameraControl constructor for the corresponding call to
* NS_ADDREF_THIS().
*/
NS_RELEASE(mDOMCameraControl);
return NS_OK;
}
protected:
/**
* 'mDOMCameraControl' is a raw pointer to a previously ADDREF()ed object,
* which is released in Run().
*/
nsDOMCameraControl* mDOMCameraControl;
nsresult mResult;
nsMainThreadPtrHandle<nsICameraGetCameraCallback> mOnSuccessCb;
nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
uint64_t mWindowId;
};
nsresult
nsDOMCameraControl::Result(nsresult aResult,
const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess,
const nsMainThreadPtrHandle<nsICameraErrorCallback>& onError,
uint64_t aWindowId)
{
nsCOMPtr<GetCameraResult> getCameraResult = new GetCameraResult(this, aResult, onSuccess, onError, aWindowId);
return NS_DispatchToMainThread(getCameraResult);
}
void
nsDOMCameraControl::Shutdown()
{
DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
mCameraControl->Shutdown();
}
nsRefPtr<ICameraControl>
nsDOMCameraControl::GetNativeCameraControl()
{
return mCameraControl;
}
already_AddRefed<nsHashPropertyBag>
nsDOMCameraControl::CreateRecordingDeviceEventsSubject()
{
MOZ_ASSERT(mWindow);
nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), true);
props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), true);
nsCOMPtr<nsIDocShell> docShell = mWindow->GetDocShell();
if (docShell) {
bool isApp;
DebugOnly<nsresult> rv = docShell->GetIsApp(&isApp);
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsString requestURL;
if (isApp) {
rv = docShell->GetAppManifestURL(requestURL);
MOZ_ASSERT(NS_SUCCEEDED(rv));
} else {
nsCString pageURL;
nsCOMPtr<nsIURI> docURI = mWindow->GetDocumentURI();
MOZ_ASSERT(docURI);
rv = docURI->GetSpec(pageURL);
MOZ_ASSERT(NS_SUCCEEDED(rv));
requestURL = NS_ConvertUTF8toUTF16(pageURL);
}
props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
}
return props.forget();
}