mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:30:27 +00:00
7f8ba9c1d7
- Bug 1236786 - [WebGL2] pass getVertexAttrib in gl-object-get-calls.html, r=jgilbert (60a2c91a38)
- Bug 1233046 - Fix OES_texture_float on OSX. - r=jrmuizel (4bc0059f5f)
- Bug 1233557 - Allow RGB8 to be renderable again for web-compat. - r=jrmuizel (4c13bfd8e8)
- Bug 1233549. Disallow ES3 compressed texture formats. r=jgilbert (1073033161)
- Bug 1241702 - Allow unsized DEPTH_STENCIL for RBs in WebGL 2. - r=kamidphish (87d17d2cf9)
- Bug 1239126. Handle gl_InstanceID attribute with no location. r=jgilbert (4894997e98)
- Bug 1236782 - [WebGL2] pass getProgramParameter in gl-object-get-calls.html; r=jgilbert (2136fcce48)
- Bug 1232462. Only ask for a higher version of GLSL when using WebGL2. r=jgilbert (0317be4eb4)
- Bug 1242330 - "Four extensions were promoted to core in WebGL 2 and should no longer be available as extensions." r=jgilbert r=jmuizelaar (6df020b8d4)
- Bug 1233626 - Default MaxDrawingBuffers to 1 unless ext/webgl2. - r=jrmuizel (a7580d661c)
- Bug 1231657. Don't allow linking different versions shaders. r=jgilbert (e610f98066)
- Bug 1241777 - TexCompareFunc should be stored in ascending order. r=jgilbert (b6151a0076)
- Bug 1228885 - Implement WebGLTexture::MemoryUsage. - r=kamidphish (ea06815414)
- Bug 1239259 - Fix WebGL2 generateMipmap checking. r=jgilbert (39f587c421)
- Bug 1242347 - Allow unsized internal format when generate mipmap. r=jgilbert (b203a8898c)
- Bug 1232502. Use the correct internalFormat when calling CopyTexImage2D. r=jgilbert (eeaef3215e)
- Bug 1243663 - Max uniform and attribute location lengths in WebGL2 should be 1024. r=jgilbert (c4ec6de507)
- Bug 1239488 - Add int/uint to vertex attrib data type. r=jgilbert (11b4968025)
- Bug 1184242 - Remove aTabParent != sActiveTabParent warning from IMEStateManager::SetInputContextForChildProcess. r=masayuki (0fcda10e15)
- Bug 1178652 - Send NOTIFY_IME_OF_COMPOSITION_UPDATE to parent process correctly. r=masayuki (bce28e2c91)
- Bug 1107782 - Only accept certain mouse, gamepad events as user-active. r=smaug (00542c80b9)
- Bug 1247850 - Shrink NameTableKey in nsStaticCaseInsensitiveNameTable. r=froydnj,erahm. (ce3cb3edfb)
- Bug 1247359 - micro-optimize the common case of String{Begins,End}With; r=erahm (333e042b31)
- Bug 1239125. Add operator!=(char_type*) to nsTSubstring. r=froydnj (0cc047a9a1)
- Bug 1213862 - Align nsString whitespace handling with web specs; r=froydnj (db5b11ca52)
- Bug 1141884 - Trigger compositor smooth scrolling to snap points when APZ is enabled. r=mstange,kip (593af59f2a)
- Bug 1244582: Add back in a null check that was accidentally removed. r=smaug (76bff1b01f)
- Bug 1234176 - Introduce and use the WriteSysFile() helper function. r=dhylands (22a46fbe8b)
- missing bit of Bug 1198124 - Enable -Wshadow (f84535a7a2)
- Bug 1249171 - Simplify nsCOMArray::SizeOfExcludingThis(). r=erahm. (57efdce1c6)
- Bug 1156416 - Validate camera parameters supplied by the application. r=mikeh (f8b4b84ccf)
- Bug 1186808 - Replace nsBaseHashtable::EnumerateRead() calls in dom/camera/ with iterators. r=mikeh. (7b1db5f6a1)
- Bug 1158378 - Fix how a failed set configuration call would try to shutdown the camera after release. (9d5e323bca)
- Bug 1171374 - Permit software video codecs with the emulated camera. r=sotaro (c1ae26ea0d)
- Bug 1234458 P1 Allow the CacheChild to be "locked" into memory so it will delay destruction. r=ehsan a=ritu (9e46185779)
- Bug 1234458 P2 Lock the CacheChild actor while Cache DOM methods are running. r=ehsan a=ritu (038342a6e2)
- Bug 1244764 P1 Make Cache .add()/.addAll() fail if a Response.ok() is false. r=ehsan (ae26ca9ef1)
- Bug 1172562 - Clear QuotaManager storage when uninstalling an app. Test. r=bkelly (b07311a3b7)
- Bug 1172629 - Use the caches global property from an iframe loaded after setting the pref in order to make the tests pass with the pref disabled; r=bkelly a=RyanVM (e7c05d8b79)
- Bug 1244764 P2 Make dom/cache mochitests pass with new add()/addAll() behavior. r=ehsan (e1f667c1b4)
- Bug 1244764 P3 Make service worker tests pass with new Cache add()/addAll() behavior. r=ehsan (1518ae5225)
- Bug 1003860 - Simplify storage setup tasks in storage inspector tests. r=mratcliffe (249a8bdb2b)
- Bug 1003860 - Service worker cache for storage actor. r=mratcliffe (5c3d1ecd0c)
- Bug 1244764 P5 Fix devtools test to work with new Cache add()/addAll() behavior. r=ehsan (bf85405de8)
- Bug 1232901 - Use channel.asyncOpen2 within dom/browser-element/BrowserElementParent.js (r=sicking,aus) (2a228ed551)
- Bug 1180330 - http auth prompt shown when opening browser if prompt canceled/dismissed earlier. r=fabrice (ba3666f4bd)
- Bug 1234118 - Delete code for supporting 'do-command' and 'copypaste-docommand'. r=mtseng, r=smaug (b1b575d3c5)
- Bug 1238883 - [TV Browser] It shows "The page cannot be displayed" when user browse some webpages. r=roc (e6d7739dd6)
- Bug 1238440 - FileReader should throw an error when the blob changed size when reading, r=khuey (b006adba10)
- Bug 1230422 - FileReader should handle nested ReadAs*() calls. r=khuey (5a3ff84a31)
- Bug 1225202, part 3 - Create files in test_fileapi_slice.html using SpecialPowers.createFiles. r=baku (1137975548)
- Bug 1241171 - FormData should not force 'blob' as filename, r=smaug (748055f751)
- Bug 1246375 - Restore the previous spec version of FormData, r=smaug (3586af2b88)
- Bug 1237183 - Modify implementation of reading preference. r=seanlin (a132bc7246)
- Bug 801545 - Remove DocumentType.internalSubset, r=bz (ea30c9b5ee)
- Bug 1226440 - Expose a method to get a node's immediate dominator; r=bz,sfink (f77ae44037)
- Bug 825318 - Implement adoptDownload for mozDownloadManager, r=aus, r=sicking (e98cb05210)
- Bug 1237370 - Always log the reason for remote AppRep lookup failures. r=gcp (2c804e68fc)
- Bug 1167493 - Application Reputation: disable remote lookup of zip files on Mac/Linux, r=gcp (517459e064)
- Bug 1195519 - Use channel->ascynOpen2 toolkit/components/downloads/ApplicationReputation.cpp (r=sicking) (2856e5213a)
- Bug 1237856 - Add prefs to honor/ignore Application Reputation verdicts. r=gcp (54ee06264f)
- Bug 1243643 - Deprecate unsafe CPOW usage in contentAreaUtils' saveImage. r=jld (6ae790f1ef)
- Bug 1229224: Add an eslint plugin for importing all browser.js globals for browser-chrome tests. r=miker (9df52a7f3b)
- Bug 1245916: Add additional browser window scripts to eslint globals. r=felipe (92d316ca5e)
- Bug 1246244 - Allow non-CPOW documents to pass through saveImageURL properly. r=jaws,Margaret (c8d4ca241d)
- some missing bits after world fix (c0439eebb0)
- add some missing stuff (ddbd47dc03)
- bissing bit of 1229519 (4e255c3dae)
- Bug 1199662 - Crash ping environment block is broken when any string field contains a quotation mark. Unescape INI fields properly using the library that already exists for the purpose. r=ted (874a999edc)
- Bug 1216150 - Turn on the experimental Intl.DateTimeFormat.prototype.formatToParts in b2g certified apps. r=fabrice (40eeb1a4d4)
- Bug 1216150 - Mini-bustage fix for something I think I unintentionally qref'd into the final patch. r=bustage in a CLOSED TREE (36d9b21a67)
- Bug 1141311 - Add async mode support to GonkNativeWindow on Lollipop Gonk r=pchang (39d9d56326)
- Bug 1146671 - Ensure camera not already released when performing operations. r=dhylands (71b59caa1f)
- Bug 1248737. Improve documentation for WorkerRunnable and associated classes. r=khuey (4ff57790c5)
- Bug 1235629 - Remove dead code in WorkerFeature.h, r=smaug (75a51fcf03)
- Bug 1212333 - WorkerDebuggerManager should live on the main thread;r=khuey (11fdfbbae6)
- Bug 1226443 P3 Re-enable service worker update wpt tests. r=ehsan (605dac5f9e)
- Bug 1226443 P4 Cleanup ServiceWorkerScriptCache objects when initialization fails. r=ehsan (43de3429a2)
- Bug 1234127: Change |BluetoothAdapter.pairingReqs| as a nullable object; r=btian, r=mrbkap (45d2038f6a)
- Bug 1188487 - BrowserElement webidl changes for muting and setting volume. r=ehsan (21bea70a07)
- Bug 1238210 - Correct the Promise return types on two Clients methods; r=baku (fa41b25df0)
- Bug 1246784 - Expose Console to the WorkerDebuggerGlobalScope - part 2, r=khuey (0da9ce8ff6)
- Bug 1228702. Don't expose the 'location' property of Exception/DOMException on workers. r=bholley (0fe86ea586)
- Bug 1223825 - Change Directory.path to include the directory's name. r=baku (0cdae4c2f0)
- Bug 1238225 - Mark ExtendableMessageEvent.ports as SameObject; r=baku (45b9a9746f)
- Bug 1236933 - Return null from FetchEvent.clientId for non-subresource network requests; r=bkelly (4a9c4b40cb)
- Bug 1238213 - Make FetchEvent.request non-nullable; r=baku (751082c8ba)
- Bug 1193125 - Avoid corrupting image data in test_fetch_event.html. r=bkelly (9f6bff232f)
- Bug 1201664 - Avoid using Request's constructor when creating FetchEvent.request; r=bkelly (7a3401e345)
- Bug 1175944 - Packaged app's (app://) JS files are not loaded and do not trigger "onfetch" handler. r=jdm (62df139153)
- Bug 1233644 - use pattern matching when listening clear-origin-data. r=baku (ea2594f50e)
- Bug 1237363 - Part 1: Unregister all service workers registered in mochitests at the end of the test; r=jdm (5be97e5bb0)
- Bug 1237363 - Part 2: Fail mochitests which register a service worker without unregistering it; r=jdm (c4160ffd5f)
- Bug 1237363 - Part 3: Add a test for a mochitest finishing without unregistering its service worker; r=jdm (911d37291b)
- Bug 1174078 - Calling "fetch" inside Service Worker's "onfetch" handler in b2g causes "onfetch" again that leads to an infinite loop. Test. r=nsm (208451f346)
- Bug 1197379 - Remove support for intercepting app:// URIs using service workers; r=jdm (3cbdd725f1)
- Bug 1179399 - Part 1: Relax the ShouldIntercept checks when overriding JAR channel info; r=jdm (850bb2bdb8)
- Bug 1238213 follow-up: Mark the FetchEventInit dictionary argument to FetchEvent's constructor optional too; r=bzbarsky (356cbe6db7)
- Bug 1232732 - modify NS_WARNING in MOZ_WIN_MEM_TRY_CATCH; r=aklotz (e2be4d6919)
- Bug 1247658 - Expose a method to JS for find the shortest retaining paths of some nodes in a heap snapshot; r=bz r=jimb (2c82198808)
- Bug 1188115: Expose IDBCursorWithValue in workers. r=baku (e1c40aeb6e)
- Bug 1162680 - Notify Keyboard.jsm to send blur event when the message manager is closed first. r=timdream (53727ab300)
- Bug 1192986 Also mark Cache/CacheStorage as release interfaces on workers. r=ehsan a=bustage (25cf83c154)
- Bug 1159742. Get rid of the pref annotation from test_interfaces, since it basically corresponds to disabling the test. r=jst (c229e3f881)
- Bug 1203160 - Part 2: Fix the interfaces tests to allow SW interfaces for non-release Fennec; r=baku (072840db1f)
- Bug 1197700 - Correct mistakes in InputMethod.webidl. r=kanru, r=janjongboom, sr=smaug (4edb6f201f)
- Bug 1206970 - Stop expecting AnimationPlaybackEvent to be exposed on release branches, where it's disabled by pref, r=smaug (30ae2b13db)
- Bug 1177276 - Pref on canvas.captureStream by default. r=smaug,mt (0cfe0f72f2)
- Bug 1215147 - Enable VR API's on FF for Android by default. r=snorp, r=vlad, r=bz (5ff3725318)
- Bug 1218482 - Enable WebVR By Default,r=bz (f26111ed82)
- Bug 1159755. Stop forcing the media.eme.apiVisible preference to be true in our test harness. r=cpearce (09f7887917)
- Bug 1149312 - Obtain test coverage for the file-backed case of MediaRecorder. r=roc (bd2e7e40f0)
- Bug 1154559 - Remove flaky timeouts from manifest.js and register SimpleTest.registerCleanupFunction() to report unfinished tests. r=cpearce. (eb68db0fb2)
- Bug 1154564 - Add the ability to notify timeouts to MediaTestManager and remove flaky timeouts from test_playback.html. r=cpearce. (c89b4e58d9)
- Bug 1135170 - Fix up racey test_seek-1.html. rpending=mattwoodrow (b3a7d0dcd6)
- Bug 902686 - Change manifest.js to use SpecialPowers.pushPrefEnv. r=edwin (636b0edc1a)
- Bug 1183502 - give androidVersion a correct value in manifest.js. r=sotaro. (933e9ea712)
- Bug 1235588 - add null check to SimpleTest. r=bechen. (958ede68de)
- misspatch (c8922447ff)
- Bug 1151740 - pass the callback object as-is to SpecialPowers.exactGC(). r=edwin (99ca873bce)
- Bug 1197682 - InputMethodManager#setSupportsSwitchingTypes, r=janjongboom, sr=smaug (e7eb54e491)
- Bug 1201407 - Add input-manage-only events for InputMethod API. r=janjongboom, sr=smaug (776d064bd1)
- Bug 1234459 - Expose full text in the input box to InputMethod API, r=masayuki, sr=smaug (4fa0554356)
- Bug 1198163 - Workaround Mochitest app and assign frame proper permissions, r=kanru (c3bcf8ecc1)
- Bug 990250 - Fold nsIStyleSheet into CSSStyleSheet. r=dbaron (23579cb300)
1756 lines
52 KiB
C++
1756 lines
52 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 "DOMCameraControl.h"
|
|
#include "base/basictypes.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsDOMClassInfo.h"
|
|
#include "nsHashPropertyBag.h"
|
|
#include "nsThread.h"
|
|
#include "DeviceStorage.h"
|
|
#include "DeviceStorageFileDescriptor.h"
|
|
#include "mozilla/dom/File.h"
|
|
#include "mozilla/dom/TabChild.h"
|
|
#include "mozilla/ipc/FileDescriptorUtils.h"
|
|
#include "mozilla/MediaManager.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/unused.h"
|
|
#include "nsIAppsService.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIDOMEventListener.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "Navigator.h"
|
|
#include "nsXULAppAPI.h"
|
|
#include "DOMCameraManager.h"
|
|
#include "DOMCameraCapabilities.h"
|
|
#include "CameraCommon.h"
|
|
#include "nsGlobalWindow.h"
|
|
#include "CameraPreviewMediaStream.h"
|
|
#include "mozilla/dom/CameraUtilBinding.h"
|
|
#include "mozilla/dom/CameraControlBinding.h"
|
|
#include "mozilla/dom/CameraManagerBinding.h"
|
|
#include "mozilla/dom/CameraCapabilitiesBinding.h"
|
|
#include "mozilla/dom/CameraConfigurationEvent.h"
|
|
#include "mozilla/dom/CameraConfigurationEventBinding.h"
|
|
#include "mozilla/dom/CameraFacesDetectedEvent.h"
|
|
#include "mozilla/dom/CameraFacesDetectedEventBinding.h"
|
|
#include "mozilla/dom/CameraStateChangeEvent.h"
|
|
#include "mozilla/dom/CameraClosedEvent.h"
|
|
#include "mozilla/dom/VideoStreamTrack.h"
|
|
#include "mozilla/dom/BlobEvent.h"
|
|
#include "DOMCameraDetectedFace.h"
|
|
#include "mozilla/dom/BindingUtils.h"
|
|
#include "nsPrintfCString.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::ipc;
|
|
|
|
class mozilla::TrackCreatedListener : public MediaStreamListener
|
|
{
|
|
public:
|
|
explicit TrackCreatedListener(nsDOMCameraControl* aCameraControl)
|
|
: mCameraControl(aCameraControl) {}
|
|
|
|
void Forget() { mCameraControl = nullptr; }
|
|
|
|
void DoNotifyTrackCreated(TrackID aTrackID)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mCameraControl) {
|
|
return;
|
|
}
|
|
|
|
mCameraControl->TrackCreated(aTrackID);
|
|
}
|
|
|
|
void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
|
|
StreamTime aTrackOffset, uint32_t aTrackEvents,
|
|
const MediaSegment& aQueuedMedia,
|
|
MediaStream* aInputStream,
|
|
TrackID aInputTrackID) override
|
|
{
|
|
if (aTrackEvents & TRACK_EVENT_CREATED) {
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
NS_NewRunnableMethodWithArgs<TrackID>(
|
|
this, &TrackCreatedListener::DoNotifyTrackCreated, aID);
|
|
aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
|
|
}
|
|
}
|
|
|
|
protected:
|
|
~TrackCreatedListener() {}
|
|
|
|
nsDOMCameraControl* mCameraControl;
|
|
};
|
|
|
|
#ifdef MOZ_WIDGET_GONK
|
|
StaticRefPtr<ICameraControl> nsDOMCameraControl::sCachedCameraControl;
|
|
/* static */ nsresult nsDOMCameraControl::sCachedCameraControlStartResult = NS_OK;
|
|
/* static */ nsCOMPtr<nsITimer> nsDOMCameraControl::sDiscardCachedCameraControlTimer;
|
|
#endif
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMCameraControl)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
|
// nsISupports is an ambiguous base of nsDOMCameraControl
|
|
// so we have to work around that.
|
|
if ( aIID.Equals(NS_GET_IID(nsDOMCameraControl)) )
|
|
foundInterface = static_cast<nsISupports*>(static_cast<void*>(this));
|
|
else
|
|
NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
|
|
|
|
NS_IMPL_ADDREF_INHERITED(nsDOMCameraControl, DOMMediaStream)
|
|
NS_IMPL_RELEASE_INHERITED(nsDOMCameraControl, DOMMediaStream)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(nsDOMCameraControl, DOMMediaStream,
|
|
mAudioChannelAgent,
|
|
mCapabilities,
|
|
mWindow,
|
|
mGetCameraPromise,
|
|
mAutoFocusPromise,
|
|
mTakePicturePromise,
|
|
mStartRecordingPromise,
|
|
mReleasePromise,
|
|
mSetConfigurationPromise)
|
|
|
|
/* static */
|
|
bool
|
|
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:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIDOMEVENTLISTENER
|
|
|
|
explicit StartRecordingHelper(nsDOMCameraControl* aDOMCameraControl)
|
|
: mDOMCameraControl(aDOMCameraControl)
|
|
, mState(false)
|
|
{
|
|
MOZ_COUNT_CTOR(StartRecordingHelper);
|
|
}
|
|
|
|
protected:
|
|
virtual ~StartRecordingHelper()
|
|
{
|
|
MOZ_COUNT_DTOR(StartRecordingHelper);
|
|
mDOMCameraControl->OnCreatedFileDescriptor(mState);
|
|
}
|
|
|
|
protected:
|
|
RefPtr<nsDOMCameraControl> mDOMCameraControl;
|
|
bool mState;
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
StartRecordingHelper::HandleEvent(nsIDOMEvent* aEvent)
|
|
{
|
|
nsString eventType;
|
|
aEvent->GetType(eventType);
|
|
mState = eventType.EqualsLiteral("success");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(mozilla::StartRecordingHelper, nsIDOMEventListener)
|
|
|
|
nsDOMCameraControl::DOMCameraConfiguration::DOMCameraConfiguration()
|
|
: CameraConfiguration()
|
|
, mMaxFocusAreas(0)
|
|
, mMaxMeteringAreas(0)
|
|
{
|
|
MOZ_COUNT_CTOR(nsDOMCameraControl::DOMCameraConfiguration);
|
|
}
|
|
|
|
nsDOMCameraControl::DOMCameraConfiguration::DOMCameraConfiguration(const CameraConfiguration& aConfiguration)
|
|
: CameraConfiguration(aConfiguration)
|
|
, mMaxFocusAreas(0)
|
|
, mMaxMeteringAreas(0)
|
|
{
|
|
MOZ_COUNT_CTOR(nsDOMCameraControl::DOMCameraConfiguration);
|
|
}
|
|
|
|
nsDOMCameraControl::DOMCameraConfiguration::~DOMCameraConfiguration()
|
|
{
|
|
MOZ_COUNT_DTOR(nsDOMCameraControl::DOMCameraConfiguration);
|
|
}
|
|
|
|
#ifdef MOZ_WIDGET_GONK
|
|
// This should be long enough for even our slowest platforms.
|
|
static const unsigned long kCachedCameraTimeoutMs = 3500;
|
|
|
|
// Open the battery-door-facing camera by default.
|
|
static const uint32_t kDefaultCameraId = 0;
|
|
|
|
/* static */ void
|
|
nsDOMCameraControl::PreinitCameraHardware()
|
|
{
|
|
// Assume a default, minimal configuration. This should initialize the
|
|
// hardware, but won't (can't) start the preview.
|
|
RefPtr<ICameraControl> cameraControl = ICameraControl::Create(kDefaultCameraId);
|
|
if (NS_WARN_IF(!cameraControl)) {
|
|
return;
|
|
}
|
|
|
|
sCachedCameraControlStartResult = cameraControl->Start();
|
|
if (NS_WARN_IF(NS_FAILED(sCachedCameraControlStartResult))) {
|
|
return;
|
|
}
|
|
|
|
sCachedCameraControl = cameraControl;
|
|
|
|
nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
|
|
if (NS_WARN_IF(!timer)) {
|
|
return;
|
|
}
|
|
|
|
nsresult rv = timer->InitWithFuncCallback(DiscardCachedCameraInstance,
|
|
nullptr,
|
|
kCachedCameraTimeoutMs,
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
// If we can't start the timer, it's possible for an app to never grab the
|
|
// camera, leaving the hardware tied up indefinitely. Better to take the
|
|
// performance hit.
|
|
sCachedCameraControl = nullptr;
|
|
return;
|
|
}
|
|
|
|
sDiscardCachedCameraControlTimer = timer;
|
|
}
|
|
|
|
/* static */ void
|
|
nsDOMCameraControl::DiscardCachedCameraInstance(nsITimer* aTimer, void* aClosure)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
sDiscardCachedCameraControlTimer = nullptr;
|
|
sCachedCameraControl = nullptr;
|
|
}
|
|
#endif
|
|
|
|
nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
|
|
const CameraConfiguration& aInitialConfig,
|
|
Promise* aPromise,
|
|
nsPIDOMWindow* aWindow)
|
|
: DOMMediaStream()
|
|
, mCameraControl(nullptr)
|
|
, mAudioChannelAgent(nullptr)
|
|
, mGetCameraPromise(aPromise)
|
|
, mWindow(aWindow)
|
|
, mPreviewState(CameraControlListener::kPreviewStopped)
|
|
, mRecording(false)
|
|
, mRecordingStoppedDeferred(false)
|
|
, mSetInitialConfig(false)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
mInput = new CameraPreviewMediaStream(this);
|
|
|
|
BindToOwner(aWindow);
|
|
|
|
RefPtr<DOMCameraConfiguration> initialConfig =
|
|
new DOMCameraConfiguration(aInitialConfig);
|
|
|
|
// Create and initialize the underlying camera.
|
|
ICameraControl::Configuration config;
|
|
bool haveInitialConfig = false;
|
|
nsresult rv;
|
|
|
|
switch (aInitialConfig.mMode) {
|
|
case CameraMode::Picture:
|
|
config.mMode = ICameraControl::kPictureMode;
|
|
haveInitialConfig = true;
|
|
break;
|
|
|
|
case CameraMode::Video:
|
|
config.mMode = ICameraControl::kVideoMode;
|
|
haveInitialConfig = true;
|
|
break;
|
|
|
|
case CameraMode::Unspecified:
|
|
break;
|
|
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unanticipated camera mode!");
|
|
break;
|
|
}
|
|
|
|
if (haveInitialConfig) {
|
|
rv = SelectPreviewSize(aInitialConfig.mPreviewSize, config.mPreviewSize);
|
|
if (NS_FAILED(rv)) {
|
|
mListener->OnUserError(DOMCameraControlListener::kInStartCamera, rv);
|
|
return;
|
|
}
|
|
|
|
config.mPictureSize.width = aInitialConfig.mPictureSize.mWidth;
|
|
config.mPictureSize.height = aInitialConfig.mPictureSize.mHeight;
|
|
config.mRecorderProfile = aInitialConfig.mRecorderProfile;
|
|
}
|
|
|
|
#ifdef MOZ_WIDGET_GONK
|
|
bool gotCached = false;
|
|
if (sCachedCameraControl && aCameraId == kDefaultCameraId) {
|
|
mCameraControl = sCachedCameraControl;
|
|
sCachedCameraControl = nullptr;
|
|
gotCached = true;
|
|
} else {
|
|
sCachedCameraControl = nullptr;
|
|
#endif
|
|
mCameraControl = ICameraControl::Create(aCameraId);
|
|
#ifdef MOZ_WIDGET_GONK
|
|
}
|
|
#endif
|
|
mCurrentConfiguration = initialConfig.forget();
|
|
|
|
// Register a TrackCreatedListener directly on CameraPreviewMediaStream
|
|
// so we can know the TrackID of the video track.
|
|
mTrackCreatedListener = new TrackCreatedListener(this);
|
|
mInput->AddListener(mTrackCreatedListener);
|
|
|
|
// Register the playback listener directly on the camera input stream.
|
|
// We want as low latency as possible for the camera, thus avoiding
|
|
// MediaStreamGraph altogether. Don't do the regular InitStreamCommon()
|
|
// to avoid initializing the Owned and Playback streams. This is OK since
|
|
// we are not user/DOM facing anyway.
|
|
CreateAndAddPlaybackStreamListener(mInput);
|
|
|
|
MOZ_ASSERT(mWindow, "Shouldn't be created with a null window!");
|
|
if (mWindow->GetExtantDoc()) {
|
|
CombineWithPrincipal(mWindow->GetExtantDoc()->NodePrincipal());
|
|
}
|
|
|
|
// Register a listener for camera events.
|
|
mListener = new DOMCameraControlListener(this, mInput);
|
|
mCameraControl->AddListener(mListener);
|
|
|
|
#ifdef MOZ_WIDGET_GONK
|
|
if (!gotCached || NS_FAILED(sCachedCameraControlStartResult)) {
|
|
#endif
|
|
// Start the camera...
|
|
if (haveInitialConfig) {
|
|
rv = mCameraControl->Start(&config);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mSetInitialConfig = true;
|
|
}
|
|
} else {
|
|
rv = mCameraControl->Start();
|
|
}
|
|
#ifdef MOZ_WIDGET_GONK
|
|
} else {
|
|
if (haveInitialConfig) {
|
|
rv = mCameraControl->SetConfiguration(config);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mSetInitialConfig = true;
|
|
}
|
|
} else {
|
|
rv = NS_OK;
|
|
}
|
|
}
|
|
#endif
|
|
if (NS_FAILED(rv)) {
|
|
mListener->OnUserError(DOMCameraControlListener::kInStartCamera, rv);
|
|
}
|
|
}
|
|
|
|
nsDOMCameraControl::~nsDOMCameraControl()
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
/*invoke DOMMediaStream destroy*/
|
|
Destroy();
|
|
|
|
if (mInput) {
|
|
mInput->Destroy();
|
|
mInput = nullptr;
|
|
}
|
|
if (mTrackCreatedListener) {
|
|
mTrackCreatedListener->Forget();
|
|
mTrackCreatedListener = nullptr;
|
|
}
|
|
}
|
|
|
|
JSObject*
|
|
nsDOMCameraControl::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
|
{
|
|
return CameraControlBinding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
bool
|
|
nsDOMCameraControl::IsWindowStillActive()
|
|
{
|
|
return nsDOMCameraManager::IsWindowStillActive(mWindow->WindowID());
|
|
}
|
|
|
|
nsresult
|
|
nsDOMCameraControl::SelectPreviewSize(const CameraSize& aRequestedPreviewSize, ICameraControl::Size& aSelectedPreviewSize)
|
|
{
|
|
if (aRequestedPreviewSize.mWidth && aRequestedPreviewSize.mHeight) {
|
|
aSelectedPreviewSize.width = aRequestedPreviewSize.mWidth;
|
|
aSelectedPreviewSize.height = aRequestedPreviewSize.mHeight;
|
|
} else {
|
|
/* Use the window width and height if no preview size is provided.
|
|
Note that the width and height are actually reversed from the
|
|
camera perspective. */
|
|
int32_t width = 0;
|
|
int32_t height = 0;
|
|
float ratio = 0.0;
|
|
nsresult rv;
|
|
|
|
rv = mWindow->GetDevicePixelRatio(&ratio);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = mWindow->GetInnerWidth(&height);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = mWindow->GetInnerHeight(&width);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(width > 0);
|
|
MOZ_ASSERT(height > 0);
|
|
MOZ_ASSERT(ratio > 0.0);
|
|
aSelectedPreviewSize.width = std::ceil(width * ratio);
|
|
aSelectedPreviewSize.height = std::ceil(height * ratio);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Setter for weighted regions: { top, bottom, left, right, weight }
|
|
nsresult
|
|
nsDOMCameraControl::Set(uint32_t aKey, const Optional<Sequence<CameraRegion> >& aValue, uint32_t aLimit)
|
|
{
|
|
if (aLimit == 0) {
|
|
DOM_CAMERA_LOGI("%s:%d : aLimit = 0, nothing to do\n", __func__, __LINE__);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsTArray<ICameraControl::Region> regionArray;
|
|
if (aValue.WasPassed()) {
|
|
const Sequence<CameraRegion>& regions = aValue.Value();
|
|
uint32_t length = regions.Length();
|
|
|
|
DOM_CAMERA_LOGI("%s:%d : got %d regions (limited to %d)\n", __func__, __LINE__, length, aLimit);
|
|
if (length > aLimit) {
|
|
length = aLimit;
|
|
}
|
|
|
|
// aLimit supplied by camera library provides sane ceiling (i.e. <10)
|
|
regionArray.SetCapacity(length);
|
|
|
|
for (uint32_t i = 0; i < length; ++i) {
|
|
ICameraControl::Region* r = regionArray.AppendElement();
|
|
const CameraRegion ®ion = regions[i];
|
|
r->top = region.mTop;
|
|
r->left = region.mLeft;
|
|
r->bottom = region.mBottom;
|
|
r->right = region.mRight;
|
|
r->weight = region.mWeight;
|
|
|
|
DOM_CAMERA_LOGI("region %d: top=%d, left=%d, bottom=%d, right=%d, weight=%u\n",
|
|
i,
|
|
r->top,
|
|
r->left,
|
|
r->bottom,
|
|
r->right,
|
|
r->weight
|
|
);
|
|
}
|
|
} else {
|
|
DOM_CAMERA_LOGI("%s:%d : clear regions\n", __func__, __LINE__);
|
|
}
|
|
return mCameraControl->Set(aKey, regionArray);
|
|
}
|
|
|
|
// Getter for weighted regions: { top, bottom, left, right, weight }
|
|
nsresult
|
|
nsDOMCameraControl::Get(uint32_t aKey, nsTArray<CameraRegion>& aValue)
|
|
{
|
|
nsTArray<ICameraControl::Region> regionArray;
|
|
|
|
nsresult rv = mCameraControl->Get(aKey, regionArray);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
uint32_t length = regionArray.Length();
|
|
DOM_CAMERA_LOGI("%s:%d : got %d regions\n", __func__, __LINE__, length);
|
|
aValue.SetLength(length);
|
|
|
|
for (uint32_t i = 0; i < length; ++i) {
|
|
ICameraControl::Region& r = regionArray[i];
|
|
CameraRegion& v = aValue[i];
|
|
v.mTop = r.top;
|
|
v.mLeft = r.left;
|
|
v.mBottom = r.bottom;
|
|
v.mRight = r.right;
|
|
v.mWeight = r.weight;
|
|
|
|
DOM_CAMERA_LOGI("region %d: top=%d, left=%d, bottom=%d, right=%d, weight=%u\n",
|
|
i,
|
|
v.mTop,
|
|
v.mLeft,
|
|
v.mBottom,
|
|
v.mRight,
|
|
v.mWeight
|
|
);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
MediaStream*
|
|
nsDOMCameraControl::GetCameraStream() const
|
|
{
|
|
return mInput;
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::TrackCreated(TrackID aTrackID) {
|
|
// This track is not connected through a port.
|
|
MediaInputPort* inputPort = nullptr;
|
|
dom::VideoStreamTrack* track =
|
|
new dom::VideoStreamTrack(this, aTrackID, nsString());
|
|
RefPtr<TrackPort> port =
|
|
new TrackPort(inputPort, track,
|
|
TrackPort::InputPortOwnership::OWNED);
|
|
mTracks.AppendElement(port.forget());
|
|
NotifyTrackAdded(track);
|
|
}
|
|
|
|
#define THROW_IF_NO_CAMERACONTROL(...) \
|
|
do { \
|
|
if (!mCameraControl) { \
|
|
DOM_CAMERA_LOGW("mCameraControl is null at %s:%d\n", __func__, __LINE__); \
|
|
aRv = NS_ERROR_NOT_AVAILABLE; \
|
|
return __VA_ARGS__; \
|
|
} \
|
|
} while (0)
|
|
|
|
void
|
|
nsDOMCameraControl::GetEffect(nsString& aEffect, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_EFFECT, aEffect);
|
|
}
|
|
void
|
|
nsDOMCameraControl::SetEffect(const nsAString& aEffect, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_EFFECT, aEffect);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::GetWhiteBalanceMode(nsString& aWhiteBalanceMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
|
|
}
|
|
void
|
|
nsDOMCameraControl::SetWhiteBalanceMode(const nsAString& aWhiteBalanceMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::GetSceneMode(nsString& aSceneMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_SCENEMODE, aSceneMode);
|
|
}
|
|
void
|
|
nsDOMCameraControl::SetSceneMode(const nsAString& aSceneMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_SCENEMODE, aSceneMode);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::GetFlashMode(nsString& aFlashMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_FLASHMODE, aFlashMode);
|
|
}
|
|
void
|
|
nsDOMCameraControl::SetFlashMode(const nsAString& aFlashMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_FLASHMODE, aFlashMode);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::GetFocusMode(nsString& aFocusMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSMODE, aFocusMode);
|
|
}
|
|
void
|
|
nsDOMCameraControl::SetFocusMode(const nsAString& aFocusMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_FOCUSMODE, aFocusMode);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::GetIsoMode(nsString& aIsoMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_ISOMODE, aIsoMode);
|
|
}
|
|
void
|
|
nsDOMCameraControl::SetIsoMode(const nsAString& aIsoMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_ISOMODE, aIsoMode);
|
|
}
|
|
|
|
double
|
|
nsDOMCameraControl::GetPictureQuality(ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL(1.0);
|
|
|
|
double quality;
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_PICTURE_QUALITY, quality);
|
|
return quality;
|
|
}
|
|
void
|
|
nsDOMCameraControl::SetPictureQuality(double aQuality, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_PICTURE_QUALITY, aQuality);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::GetMeteringMode(nsString& aMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_METERINGMODE, aMode);
|
|
}
|
|
void
|
|
nsDOMCameraControl::SetMeteringMode(const nsAString& aMode, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_METERINGMODE, aMode);
|
|
}
|
|
|
|
double
|
|
nsDOMCameraControl::GetZoom(ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL(1.0);
|
|
|
|
double zoom = 1.0;
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_ZOOM, zoom);
|
|
return zoom;
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::SetZoom(double aZoom, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_ZOOM, aZoom);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::GetMeteringAreas(nsTArray<CameraRegion>& aAreas, ErrorResult& aRv)
|
|
{
|
|
aRv = Get(CAMERA_PARAM_METERINGAREAS, aAreas);
|
|
}
|
|
void
|
|
nsDOMCameraControl::SetMeteringAreas(const Optional<Sequence<CameraRegion> >& aMeteringAreas, ErrorResult& aRv)
|
|
{
|
|
aRv = Set(CAMERA_PARAM_METERINGAREAS, aMeteringAreas,
|
|
mCurrentConfiguration->mMaxMeteringAreas);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::GetFocusAreas(nsTArray<CameraRegion>& aAreas, ErrorResult& aRv)
|
|
{
|
|
aRv = Get(CAMERA_PARAM_FOCUSAREAS, aAreas);
|
|
}
|
|
void
|
|
nsDOMCameraControl::SetFocusAreas(const Optional<Sequence<CameraRegion> >& aFocusAreas, ErrorResult& aRv)
|
|
{
|
|
aRv = Set(CAMERA_PARAM_FOCUSAREAS, aFocusAreas,
|
|
mCurrentConfiguration->mMaxFocusAreas);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::GetPictureSize(CameraSize& aSize, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
|
|
ICameraControl::Size size;
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_PICTURE_SIZE, size);
|
|
if (aRv.Failed()) {
|
|
return;
|
|
}
|
|
|
|
aSize.mWidth = size.width;
|
|
aSize.mHeight = size.height;
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::SetPictureSize(const CameraSize& aSize, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
ICameraControl::Size s = { aSize.mWidth, aSize.mHeight };
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_PICTURE_SIZE, s);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::GetThumbnailSize(CameraSize& aSize, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
ICameraControl::Size size;
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_THUMBNAILSIZE, size);
|
|
if (aRv.Failed()) {
|
|
return;
|
|
}
|
|
|
|
aSize.mWidth = size.width;
|
|
aSize.mHeight = size.height;
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::SetThumbnailSize(const CameraSize& aSize, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
ICameraControl::Size s = { aSize.mWidth, aSize.mHeight };
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_THUMBNAILSIZE, s);
|
|
}
|
|
|
|
double
|
|
nsDOMCameraControl::GetFocalLength(ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL(0.0);
|
|
|
|
double focalLength;
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_FOCALLENGTH, focalLength);
|
|
return focalLength;
|
|
}
|
|
|
|
double
|
|
nsDOMCameraControl::GetFocusDistanceNear(ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL(0.0);
|
|
|
|
double distance;
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCENEAR, distance);
|
|
return distance;
|
|
}
|
|
|
|
double
|
|
nsDOMCameraControl::GetFocusDistanceOptimum(ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL(0.0);
|
|
|
|
double distance;
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, distance);
|
|
return distance;
|
|
}
|
|
|
|
double
|
|
nsDOMCameraControl::GetFocusDistanceFar(ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL(0.0);
|
|
|
|
double distance;
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEFAR, distance);
|
|
return distance;
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::SetExposureCompensation(double aCompensation, ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, aCompensation);
|
|
}
|
|
|
|
double
|
|
nsDOMCameraControl::GetExposureCompensation(ErrorResult& aRv)
|
|
{
|
|
THROW_IF_NO_CAMERACONTROL(0.0);
|
|
|
|
double compensation;
|
|
aRv = mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, compensation);
|
|
return compensation;
|
|
}
|
|
|
|
int32_t
|
|
nsDOMCameraControl::SensorAngle()
|
|
{
|
|
int32_t angle = 0;
|
|
if (mCameraControl) {
|
|
mCameraControl->Get(CAMERA_PARAM_SENSORANGLE, angle);
|
|
}
|
|
return angle;
|
|
}
|
|
|
|
already_AddRefed<dom::CameraCapabilities>
|
|
nsDOMCameraControl::Capabilities()
|
|
{
|
|
if (!mCameraControl) {
|
|
DOM_CAMERA_LOGW("mCameraControl is null at %s:%d\n", __func__, __LINE__);
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<CameraCapabilities> caps = mCapabilities;
|
|
if (!caps) {
|
|
caps = new CameraCapabilities(mWindow, mCameraControl);
|
|
mCapabilities = caps;
|
|
}
|
|
|
|
return caps.forget();
|
|
}
|
|
|
|
// Methods.
|
|
already_AddRefed<Promise>
|
|
nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
|
|
nsDOMDeviceStorage& aStorageArea,
|
|
const nsAString& aFilename,
|
|
ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
|
|
RefPtr<Promise> promise = CreatePromise(aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 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.mCreatePoster) {
|
|
promise->MaybeReject(NS_ERROR_IN_PROGRESS);
|
|
return promise.forget();
|
|
}
|
|
|
|
aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
mDSFileDescriptor = new DeviceStorageFileDescriptor();
|
|
RefPtr<DOMRequest> request = aStorageArea.CreateFileDescriptor(aFilename,
|
|
mDSFileDescriptor.get(),
|
|
aRv);
|
|
if (aRv.Failed()) {
|
|
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
|
|
aRv = RegisterStorageRequestEvents(request, listener);
|
|
if (aRv.Failed()) {
|
|
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
|
|
return nullptr;
|
|
}
|
|
|
|
mStartRecordingPromise = promise;
|
|
mOptions = aOptions;
|
|
mRecording = true;
|
|
return promise.forget();
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded)
|
|
{
|
|
nsresult rv = NS_ERROR_FAILURE;
|
|
|
|
if (!mCameraControl) {
|
|
rv = NS_ERROR_NOT_AVAILABLE;
|
|
} else if (!mRecording) {
|
|
// Race condition where StopRecording comes in before we issue
|
|
// the start recording request to Gonk
|
|
rv = NS_ERROR_ABORT;
|
|
mOptions.mCreatePoster = false;
|
|
} else if (aSucceeded && mDSFileDescriptor->mFileDescriptor.IsValid()) {
|
|
ICameraControl::StartRecordingOptions o;
|
|
|
|
o.rotation = mOptions.mRotation;
|
|
o.maxFileSizeBytes = mOptions.mMaxFileSizeBytes;
|
|
o.maxVideoLengthMs = mOptions.mMaxVideoLengthMs;
|
|
o.autoEnableLowLightTorch = mOptions.mAutoEnableLowLightTorch;
|
|
o.createPoster = mOptions.mCreatePoster;
|
|
rv = mCameraControl->StartRecording(mDSFileDescriptor.get(), &o);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
OnUserError(CameraControlListener::kInStartRecording, rv);
|
|
|
|
if (mDSFileDescriptor->mFileDescriptor.IsValid()) {
|
|
// An error occured. We need to manually close the file associated with the
|
|
// FileDescriptor, and we shouldn't do this on the main thread, so we
|
|
// use a little helper.
|
|
RefPtr<CloseFileRunnable> closer =
|
|
new CloseFileRunnable(mDSFileDescriptor->mFileDescriptor);
|
|
closer->Dispatch();
|
|
}
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::StopRecording(ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
|
|
ReleaseAudioChannelAgent();
|
|
mRecording = false;
|
|
aRv = mCameraControl->StopRecording();
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::PauseRecording(ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
|
|
aRv = mCameraControl->PauseRecording();
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::ResumeRecording(ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
|
|
aRv = mCameraControl->ResumeRecording();
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::ResumePreview(ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->StartPreview();
|
|
}
|
|
|
|
already_AddRefed<Promise>
|
|
nsDOMCameraControl::SetConfiguration(const CameraConfiguration& aConfiguration,
|
|
ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
THROW_IF_NO_CAMERACONTROL(nullptr);
|
|
|
|
RefPtr<Promise> promise = CreatePromise(aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (mTakePicturePromise) {
|
|
// We're busy taking a picture, can't change modes right now.
|
|
promise->MaybeReject(NS_ERROR_IN_PROGRESS);
|
|
return promise.forget();
|
|
}
|
|
|
|
ICameraControl::Configuration config;
|
|
aRv = SelectPreviewSize(aConfiguration.mPreviewSize, config.mPreviewSize);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
config.mRecorderProfile = aConfiguration.mRecorderProfile;
|
|
config.mPictureSize.width = aConfiguration.mPictureSize.mWidth;
|
|
config.mPictureSize.height = aConfiguration.mPictureSize.mHeight;
|
|
config.mMode = ICameraControl::kPictureMode;
|
|
if (aConfiguration.mMode == CameraMode::Video) {
|
|
config.mMode = ICameraControl::kVideoMode;
|
|
}
|
|
|
|
aRv = mCameraControl->SetConfiguration(config);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
mSetConfigurationPromise = promise;
|
|
return promise.forget();
|
|
}
|
|
|
|
already_AddRefed<Promise>
|
|
nsDOMCameraControl::AutoFocus(ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
THROW_IF_NO_CAMERACONTROL(nullptr);
|
|
|
|
RefPtr<Promise> promise = mAutoFocusPromise.forget();
|
|
if (promise) {
|
|
// There is already a call to AutoFocus() in progress, cancel it and
|
|
// invoke the error callback (if one was passed in).
|
|
promise->MaybeReject(NS_ERROR_IN_PROGRESS);
|
|
}
|
|
|
|
promise = CreatePromise(aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
aRv = mCameraControl->AutoFocus();
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("focusing"));
|
|
|
|
mAutoFocusPromise = promise;
|
|
return promise.forget();
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::StartFaceDetection(ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->StartFaceDetection();
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::StopFaceDetection(ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->StopFaceDetection();
|
|
}
|
|
|
|
already_AddRefed<Promise>
|
|
nsDOMCameraControl::TakePicture(const CameraPictureOptions& aOptions,
|
|
ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
THROW_IF_NO_CAMERACONTROL(nullptr);
|
|
|
|
RefPtr<Promise> promise = CreatePromise(aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (mTakePicturePromise) {
|
|
// There is already a call to TakePicture() in progress, abort this new
|
|
// one and invoke the error callback (if one was passed in).
|
|
promise->MaybeReject(NS_ERROR_IN_PROGRESS);
|
|
return promise.forget();
|
|
}
|
|
|
|
{
|
|
ICameraControlParameterSetAutoEnter batch(mCameraControl);
|
|
|
|
// XXXmikeh - remove this: see bug 931155
|
|
ICameraControl::Size s;
|
|
s.width = aOptions.mPictureSize.mWidth;
|
|
s.height = aOptions.mPictureSize.mHeight;
|
|
|
|
ICameraControl::Position p;
|
|
p.latitude = aOptions.mPosition.mLatitude;
|
|
p.longitude = aOptions.mPosition.mLongitude;
|
|
p.altitude = aOptions.mPosition.mAltitude;
|
|
p.timestamp = aOptions.mPosition.mTimestamp;
|
|
|
|
if (s.width && s.height) {
|
|
mCameraControl->Set(CAMERA_PARAM_PICTURE_SIZE, s);
|
|
}
|
|
if (!aOptions.mFileFormat.IsEmpty()) {
|
|
mCameraControl->Set(CAMERA_PARAM_PICTURE_FILEFORMAT, aOptions.mFileFormat);
|
|
}
|
|
mCameraControl->Set(CAMERA_PARAM_PICTURE_ROTATION, aOptions.mRotation);
|
|
mCameraControl->Set(CAMERA_PARAM_PICTURE_DATETIME, aOptions.mDateTime);
|
|
mCameraControl->SetLocation(p);
|
|
}
|
|
|
|
aRv = mCameraControl->TakePicture();
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
mTakePicturePromise = promise;
|
|
return promise.forget();
|
|
}
|
|
|
|
already_AddRefed<Promise>
|
|
nsDOMCameraControl::ReleaseHardware(ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
|
|
RefPtr<Promise> promise = CreatePromise(aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!mCameraControl) {
|
|
// Always succeed if the camera instance is already closed.
|
|
promise->MaybeResolve(JS::UndefinedHandleValue);
|
|
return promise.forget();
|
|
}
|
|
|
|
aRv = mCameraControl->Stop();
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Once we stop the camera, there's nothing we can do with it,
|
|
// so we can throw away this reference. (This won't prevent us
|
|
// from receiving the last underlying events.)
|
|
mCameraControl = nullptr;
|
|
mReleasePromise = promise;
|
|
|
|
return promise.forget();
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::ResumeContinuousFocus(ErrorResult& aRv)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
THROW_IF_NO_CAMERACONTROL();
|
|
aRv = mCameraControl->ResumeContinuousFocus();
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::Shutdown()
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
|
|
// Remove any pending solicited event handlers; these
|
|
// reference our window object, which in turn references
|
|
// us. If we don't remove them, we can leak DOM objects.
|
|
AbortPromise(mGetCameraPromise);
|
|
AbortPromise(mAutoFocusPromise);
|
|
AbortPromise(mTakePicturePromise);
|
|
AbortPromise(mStartRecordingPromise);
|
|
AbortPromise(mReleasePromise);
|
|
AbortPromise(mSetConfigurationPromise);
|
|
|
|
if (mCameraControl) {
|
|
mCameraControl->Stop();
|
|
mCameraControl = nullptr;
|
|
}
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::ReleaseAudioChannelAgent()
|
|
{
|
|
#ifdef MOZ_B2G
|
|
if (mAudioChannelAgent) {
|
|
mAudioChannelAgent->NotifyStoppedPlaying();
|
|
mAudioChannelAgent = nullptr;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
nsresult
|
|
nsDOMCameraControl::NotifyRecordingStatusChange(const nsString& aMsg)
|
|
{
|
|
NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
|
|
|
|
if (aMsg.EqualsLiteral("shutdown")) {
|
|
ReleaseAudioChannelAgent();
|
|
}
|
|
|
|
nsresult rv = MediaManager::NotifyRecordingStatusChange(mWindow,
|
|
aMsg,
|
|
true /* aIsAudio */,
|
|
true /* aIsVideo */);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
#ifdef MOZ_B2G
|
|
if (aMsg.EqualsLiteral("starting") && !mAudioChannelAgent) {
|
|
mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
|
|
if (!mAudioChannelAgent) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
// Camera app will stop recording when it falls to the background, so no callback is necessary.
|
|
mAudioChannelAgent->Init(mWindow, (int32_t)AudioChannel::Content, nullptr);
|
|
// Video recording doesn't output any sound, so it's not necessary to check canPlay.
|
|
float volume = 0.0;
|
|
bool muted = true;
|
|
rv = mAudioChannelAgent->NotifyStartedPlaying(&volume, &muted);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
#endif
|
|
return rv;
|
|
}
|
|
|
|
already_AddRefed<Promise>
|
|
nsDOMCameraControl::CreatePromise(ErrorResult& aRv)
|
|
{
|
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
|
|
if (!global) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return nullptr;
|
|
}
|
|
return Promise::Create(global, aRv);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::AbortPromise(RefPtr<Promise>& aPromise)
|
|
{
|
|
RefPtr<Promise> promise = aPromise.forget();
|
|
if (promise) {
|
|
promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::EventListenerAdded(nsIAtom* aType)
|
|
{
|
|
if (aType == nsGkAtoms::onpreviewstatechange) {
|
|
DispatchPreviewStateEvent(mPreviewState);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::DispatchPreviewStateEvent(CameraControlListener::PreviewState aState)
|
|
{
|
|
nsString state;
|
|
switch (aState) {
|
|
case CameraControlListener::kPreviewStarted:
|
|
state = NS_LITERAL_STRING("started");
|
|
break;
|
|
|
|
default:
|
|
state = NS_LITERAL_STRING("stopped");
|
|
break;
|
|
}
|
|
|
|
DispatchStateEvent(NS_LITERAL_STRING("previewstatechange"), state);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::DispatchStateEvent(const nsString& aType, const nsString& aState)
|
|
{
|
|
CameraStateChangeEventInit eventInit;
|
|
eventInit.mNewState = aState;
|
|
|
|
RefPtr<CameraStateChangeEvent> event =
|
|
CameraStateChangeEvent::Constructor(this, aType, eventInit);
|
|
|
|
DispatchTrustedEvent(event);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::OnGetCameraComplete()
|
|
{
|
|
// The hardware is open, so we can return a camera to JS, even if
|
|
// the preview hasn't started yet.
|
|
RefPtr<Promise> promise = mGetCameraPromise.forget();
|
|
if (promise) {
|
|
CameraGetPromiseData data;
|
|
data.mCamera = this;
|
|
data.mConfiguration = *mCurrentConfiguration;
|
|
promise->MaybeResolve(data);
|
|
}
|
|
}
|
|
|
|
// Camera Control event handlers--must only be called from the Main Thread!
|
|
void
|
|
nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState aState,
|
|
nsresult aReason)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
switch (aState) {
|
|
case CameraControlListener::kHardwareOpen:
|
|
DOM_CAMERA_LOGI("DOM OnHardwareStateChange: open\n");
|
|
MOZ_ASSERT(aReason == NS_OK);
|
|
if (!mSetInitialConfig) {
|
|
// The hardware is open, so we can return a camera to JS, even if
|
|
// the preview hasn't started yet.
|
|
OnGetCameraComplete();
|
|
}
|
|
break;
|
|
|
|
case CameraControlListener::kHardwareClosed:
|
|
DOM_CAMERA_LOGI("DOM OnHardwareStateChange: closed\n");
|
|
if (!mSetInitialConfig) {
|
|
RefPtr<Promise> promise = mReleasePromise.forget();
|
|
if (promise) {
|
|
promise->MaybeResolve(JS::UndefinedHandleValue);
|
|
}
|
|
|
|
CameraClosedEventInit eventInit;
|
|
switch (aReason) {
|
|
case NS_OK:
|
|
eventInit.mReason = NS_LITERAL_STRING("HardwareReleased");
|
|
break;
|
|
|
|
case NS_ERROR_FAILURE:
|
|
eventInit.mReason = NS_LITERAL_STRING("SystemFailure");
|
|
break;
|
|
|
|
case NS_ERROR_NOT_AVAILABLE:
|
|
eventInit.mReason = NS_LITERAL_STRING("NotAvailable");
|
|
break;
|
|
|
|
default:
|
|
DOM_CAMERA_LOGE("Unhandled hardware close reason, 0x%x\n", aReason);
|
|
MOZ_ASSERT_UNREACHABLE("Unanticipated reason for hardware close");
|
|
eventInit.mReason = NS_LITERAL_STRING("SystemFailure");
|
|
break;
|
|
}
|
|
|
|
RefPtr<CameraClosedEvent> event =
|
|
CameraClosedEvent::Constructor(this,
|
|
NS_LITERAL_STRING("close"),
|
|
eventInit);
|
|
DispatchTrustedEvent(event);
|
|
} else {
|
|
// The configuration failed and we forced the camera to shutdown.
|
|
OnUserError(DOMCameraControlListener::kInStartCamera, NS_ERROR_NOT_AVAILABLE);
|
|
}
|
|
break;
|
|
|
|
case CameraControlListener::kHardwareOpenFailed:
|
|
DOM_CAMERA_LOGI("DOM OnHardwareStateChange: open failed\n");
|
|
MOZ_ASSERT(aReason == NS_ERROR_NOT_AVAILABLE);
|
|
OnUserError(DOMCameraControlListener::kInStartCamera, NS_ERROR_NOT_AVAILABLE);
|
|
break;
|
|
|
|
case CameraControlListener::kHardwareUninitialized:
|
|
break;
|
|
|
|
default:
|
|
DOM_CAMERA_LOGE("DOM OnHardwareStateChange: UNKNOWN=%d\n", aState);
|
|
MOZ_ASSERT_UNREACHABLE("Unanticipated camera hardware state");
|
|
}
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::OnShutter()
|
|
{
|
|
DOM_CAMERA_LOGI("DOM ** SNAP **\n");
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
DispatchTrustedEvent(NS_LITERAL_STRING("shutter"));
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::OnPreviewStateChange(CameraControlListener::PreviewState aState)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mPreviewState = aState;
|
|
nsString state;
|
|
switch (aState) {
|
|
case CameraControlListener::kPreviewStarted:
|
|
state = NS_LITERAL_STRING("started");
|
|
break;
|
|
|
|
default:
|
|
state = NS_LITERAL_STRING("stopped");
|
|
break;
|
|
}
|
|
|
|
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.mCreatePoster);
|
|
|
|
RefPtr<Blob> blob = Blob::Create(GetParentObject(), aPoster);
|
|
if (NS_WARN_IF(!blob)) {
|
|
OnRecorderStateChange(CameraControlListener::kPosterFailed, 0, 0);
|
|
return;
|
|
}
|
|
|
|
BlobEventInit eventInit;
|
|
eventInit.mData = blob;
|
|
|
|
RefPtr<BlobEvent> event = BlobEvent::Constructor(this,
|
|
NS_LITERAL_STRING("poster"),
|
|
eventInit);
|
|
|
|
DispatchTrustedEvent(event);
|
|
OnRecorderStateChange(CameraControlListener::kPosterCreated, 0, 0);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState aState,
|
|
int32_t aArg, int32_t aTrackNum)
|
|
{
|
|
// For now, we do nothing with 'aStatus' and 'aTrackNum'.
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p, state=%u\n", __func__, __LINE__, this, aState);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsString state;
|
|
|
|
switch (aState) {
|
|
case CameraControlListener::kRecorderStarted:
|
|
{
|
|
RefPtr<Promise> promise = mStartRecordingPromise.forget();
|
|
if (promise) {
|
|
promise->MaybeResolve(JS::UndefinedHandleValue);
|
|
}
|
|
|
|
state = NS_LITERAL_STRING("Started");
|
|
}
|
|
break;
|
|
|
|
case CameraControlListener::kRecorderStopped:
|
|
if (mOptions.mCreatePoster) {
|
|
mRecordingStoppedDeferred = true;
|
|
return;
|
|
}
|
|
|
|
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
|
|
state = NS_LITERAL_STRING("Stopped");
|
|
break;
|
|
|
|
case CameraControlListener::kPosterCreated:
|
|
state = NS_LITERAL_STRING("PosterCreated");
|
|
mOptions.mCreatePoster = false;
|
|
break;
|
|
|
|
case CameraControlListener::kPosterFailed:
|
|
state = NS_LITERAL_STRING("PosterFailed");
|
|
mOptions.mCreatePoster = false;
|
|
break;
|
|
|
|
case CameraControlListener::kRecorderPaused:
|
|
state = NS_LITERAL_STRING("Paused");
|
|
break;
|
|
|
|
case CameraControlListener::kRecorderResumed:
|
|
state = NS_LITERAL_STRING("Resumed");
|
|
break;
|
|
|
|
#ifdef MOZ_B2G_CAMERA
|
|
case CameraControlListener::kFileSizeLimitReached:
|
|
state = NS_LITERAL_STRING("FileSizeLimitReached");
|
|
break;
|
|
|
|
case CameraControlListener::kVideoLengthLimitReached:
|
|
state = NS_LITERAL_STRING("VideoLengthLimitReached");
|
|
break;
|
|
|
|
case CameraControlListener::kTrackCompleted:
|
|
state = NS_LITERAL_STRING("TrackCompleted");
|
|
break;
|
|
|
|
case CameraControlListener::kTrackFailed:
|
|
state = NS_LITERAL_STRING("TrackFailed");
|
|
break;
|
|
|
|
case CameraControlListener::kMediaRecorderFailed:
|
|
state = NS_LITERAL_STRING("MediaRecorderFailed");
|
|
break;
|
|
|
|
case CameraControlListener::kMediaServerFailed:
|
|
state = NS_LITERAL_STRING("MediaServerFailed");
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unanticipated video recorder error");
|
|
return;
|
|
}
|
|
|
|
DispatchStateEvent(NS_LITERAL_STRING("recorderstatechange"), state);
|
|
|
|
if (mRecordingStoppedDeferred && !mOptions.mCreatePoster) {
|
|
mRecordingStoppedDeferred = false;
|
|
OnRecorderStateChange(CameraControlListener::kRecorderStopped, 0, 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::OnConfigurationChange(DOMCameraConfiguration* aConfiguration)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aConfiguration != nullptr);
|
|
|
|
// Update our record of the current camera configuration
|
|
mCurrentConfiguration = aConfiguration;
|
|
|
|
DOM_CAMERA_LOGI("DOM OnConfigurationChange: this=%p\n", this);
|
|
DOM_CAMERA_LOGI(" mode : %s\n",
|
|
mCurrentConfiguration->mMode == CameraMode::Video ? "video" : "picture");
|
|
DOM_CAMERA_LOGI(" maximum focus areas : %d\n",
|
|
mCurrentConfiguration->mMaxFocusAreas);
|
|
DOM_CAMERA_LOGI(" maximum metering areas : %d\n",
|
|
mCurrentConfiguration->mMaxMeteringAreas);
|
|
DOM_CAMERA_LOGI(" preview size (w x h) : %d x %d\n",
|
|
mCurrentConfiguration->mPreviewSize.mWidth, mCurrentConfiguration->mPreviewSize.mHeight);
|
|
DOM_CAMERA_LOGI(" picture size (w x h) : %d x %d\n",
|
|
mCurrentConfiguration->mPictureSize.mWidth, mCurrentConfiguration->mPictureSize.mHeight);
|
|
DOM_CAMERA_LOGI(" recorder profile : %s\n",
|
|
NS_ConvertUTF16toUTF8(mCurrentConfiguration->mRecorderProfile).get());
|
|
|
|
if (mSetInitialConfig) {
|
|
OnGetCameraComplete();
|
|
mSetInitialConfig = false;
|
|
return;
|
|
}
|
|
|
|
RefPtr<Promise> promise = mSetConfigurationPromise.forget();
|
|
if (promise) {
|
|
promise->MaybeResolve(*aConfiguration);
|
|
}
|
|
|
|
CameraConfigurationEventInit eventInit;
|
|
eventInit.mMode = mCurrentConfiguration->mMode;
|
|
eventInit.mRecorderProfile = mCurrentConfiguration->mRecorderProfile;
|
|
eventInit.mPreviewSize = new DOMRect(static_cast<DOMMediaStream*>(this), 0, 0,
|
|
mCurrentConfiguration->mPreviewSize.mWidth,
|
|
mCurrentConfiguration->mPreviewSize.mHeight);
|
|
eventInit.mPictureSize = new DOMRect(static_cast<DOMMediaStream*>(this), 0, 0,
|
|
mCurrentConfiguration->mPictureSize.mWidth,
|
|
mCurrentConfiguration->mPictureSize.mHeight);
|
|
|
|
RefPtr<CameraConfigurationEvent> event =
|
|
CameraConfigurationEvent::Constructor(this,
|
|
NS_LITERAL_STRING("configurationchanged"),
|
|
eventInit);
|
|
|
|
DispatchTrustedEvent(event);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::OnAutoFocusComplete(bool aAutoFocusSucceeded)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<Promise> promise = mAutoFocusPromise.forget();
|
|
if (promise) {
|
|
promise->MaybeResolve(aAutoFocusSucceeded);
|
|
}
|
|
|
|
if (aAutoFocusSucceeded) {
|
|
DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("focused"));
|
|
} else {
|
|
DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("unfocused"));
|
|
}
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::OnAutoFocusMoving(bool aIsMoving)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (aIsMoving) {
|
|
DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("focusing"));
|
|
}
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces)
|
|
{
|
|
DOM_CAMERA_LOGI("DOM OnFacesDetected %zu face(s)\n", aFaces.Length());
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
Sequence<OwningNonNull<DOMCameraDetectedFace> > faces;
|
|
uint32_t len = aFaces.Length();
|
|
|
|
if (faces.SetCapacity(len, fallible)) {
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
*faces.AppendElement(fallible) =
|
|
new DOMCameraDetectedFace(static_cast<DOMMediaStream*>(this), aFaces[i]);
|
|
}
|
|
}
|
|
|
|
CameraFacesDetectedEventInit eventInit;
|
|
eventInit.mFaces.SetValue(faces);
|
|
|
|
RefPtr<CameraFacesDetectedEvent> event =
|
|
CameraFacesDetectedEvent::Constructor(this,
|
|
NS_LITERAL_STRING("facesdetected"),
|
|
eventInit);
|
|
|
|
DispatchTrustedEvent(event);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::OnTakePictureComplete(nsIDOMBlob* aPicture)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aPicture);
|
|
|
|
RefPtr<Promise> promise = mTakePicturePromise.forget();
|
|
if (promise) {
|
|
nsCOMPtr<nsIDOMBlob> picture = aPicture;
|
|
promise->MaybeResolve(picture);
|
|
}
|
|
|
|
RefPtr<Blob> blob = static_cast<Blob*>(aPicture);
|
|
BlobEventInit eventInit;
|
|
eventInit.mData = blob;
|
|
|
|
RefPtr<BlobEvent> event = BlobEvent::Constructor(this,
|
|
NS_LITERAL_STRING("picture"),
|
|
eventInit);
|
|
|
|
DispatchTrustedEvent(event);
|
|
}
|
|
|
|
void
|
|
nsDOMCameraControl::OnUserError(CameraControlListener::UserContext aContext, nsresult aError)
|
|
{
|
|
DOM_CAMERA_LOGI("DOM OnUserError : this=%p, aContext=%u, aError=0x%x\n",
|
|
this, aContext, aError);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<Promise> promise;
|
|
|
|
switch (aContext) {
|
|
case CameraControlListener::kInStartCamera:
|
|
promise = mGetCameraPromise.forget();
|
|
// If we failed to open the camera, we never actually provided a reference
|
|
// for the application to release explicitly. Thus we must clear our handle
|
|
// here to ensure everything is freed.
|
|
mCameraControl = nullptr;
|
|
break;
|
|
|
|
case CameraControlListener::kInStopCamera:
|
|
promise = mReleasePromise.forget();
|
|
if (aError == NS_ERROR_NOT_INITIALIZED) {
|
|
// This value indicates that the hardware is already closed; which for
|
|
// kInStopCamera, is not actually an error.
|
|
if (promise) {
|
|
promise->MaybeResolve(JS::UndefinedHandleValue);
|
|
}
|
|
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case CameraControlListener::kInSetConfiguration:
|
|
if (mSetInitialConfig && mCameraControl) {
|
|
// If the SetConfiguration() call in the constructor fails, there
|
|
// is nothing we can do except release the camera hardware. This
|
|
// will trigger a hardware state change, and when the flag that
|
|
// got us here is set in that handler, we replace the normal reason
|
|
// code with one that indicates the hardware isn't available.
|
|
DOM_CAMERA_LOGI("Failed to configure cached camera, stopping\n");
|
|
mCameraControl->Stop();
|
|
return;
|
|
}
|
|
promise = mSetConfigurationPromise.forget();
|
|
break;
|
|
|
|
case CameraControlListener::kInAutoFocus:
|
|
promise = mAutoFocusPromise.forget();
|
|
DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("unfocused"));
|
|
break;
|
|
|
|
case CameraControlListener::kInTakePicture:
|
|
promise = mTakePicturePromise.forget();
|
|
break;
|
|
|
|
case CameraControlListener::kInStartRecording:
|
|
promise = mStartRecordingPromise.forget();
|
|
mRecording = false;
|
|
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
|
|
break;
|
|
|
|
case CameraControlListener::kInStartFaceDetection:
|
|
// This method doesn't have any callbacks, so all we can do is log the
|
|
// failure. This only happens after the hardware has been released.
|
|
NS_WARNING("Failed to start face detection");
|
|
return;
|
|
|
|
case CameraControlListener::kInStopFaceDetection:
|
|
// This method doesn't have any callbacks, so all we can do is log the
|
|
// failure. This only happens after the hardware has been released.
|
|
NS_WARNING("Failed to stop face detection");
|
|
return;
|
|
|
|
case CameraControlListener::kInResumeContinuousFocus:
|
|
// This method doesn't have any callbacks, so all we can do is log the
|
|
// failure. This only happens after the hardware has been released.
|
|
NS_WARNING("Failed to resume continuous focus");
|
|
return;
|
|
|
|
case CameraControlListener::kInStopRecording:
|
|
// This method doesn't have any callbacks, so all we can do is log the
|
|
// failure. This only happens after the hardware has been released.
|
|
NS_WARNING("Failed to stop recording");
|
|
return;
|
|
|
|
case CameraControlListener::kInPauseRecording:
|
|
// This method doesn't have any callbacks, so all we can do is log the
|
|
// failure. This only happens after the hardware has been released.
|
|
NS_WARNING("Failed to pause recording");
|
|
return;
|
|
|
|
case CameraControlListener::kInResumeRecording:
|
|
// This method doesn't have any callbacks, so all we can do is log the
|
|
// failure. This only happens after the hardware has been released.
|
|
NS_WARNING("Failed to resume recording");
|
|
return;
|
|
|
|
case CameraControlListener::kInStartPreview:
|
|
// This method doesn't have any callbacks, so all we can do is log the
|
|
// failure. This only happens after the hardware has been released.
|
|
NS_WARNING("Failed to (re)start preview");
|
|
return;
|
|
|
|
case CameraControlListener::kInStopPreview:
|
|
// This method doesn't have any callbacks, so all we can do is log the
|
|
// failure. This only happens after the hardware has been released.
|
|
NS_WARNING("Failed to stop preview");
|
|
return;
|
|
|
|
case CameraControlListener::kInSetPictureSize:
|
|
// This method doesn't have any callbacks, so all we can do is log the
|
|
// failure. This only happens after the hardware has been released.
|
|
NS_WARNING("Failed to set picture size");
|
|
return;
|
|
|
|
case CameraControlListener::kInSetThumbnailSize:
|
|
// This method doesn't have any callbacks, so all we can do is log the
|
|
// failure. This only happens after the hardware has been released.
|
|
NS_WARNING("Failed to set thumbnail size");
|
|
return;
|
|
|
|
default:
|
|
{
|
|
nsPrintfCString msg("Unhandled aContext=%u, aError=0x%x\n", aContext, aError);
|
|
NS_WARNING(msg.get());
|
|
}
|
|
MOZ_ASSERT_UNREACHABLE("Unhandled user error");
|
|
return;
|
|
}
|
|
|
|
if (!promise) {
|
|
DOM_CAMERA_LOGW("DOM No error handler for aError=0x%x in aContext=%u\n",
|
|
aError, aContext);
|
|
return;
|
|
}
|
|
|
|
promise->MaybeReject(aError);
|
|
}
|