summaryrefslogtreecommitdiffstats
path: root/dom/media/MediaDevices.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/MediaDevices.cpp')
-rw-r--r--dom/media/MediaDevices.cpp300
1 files changed, 300 insertions, 0 deletions
diff --git a/dom/media/MediaDevices.cpp b/dom/media/MediaDevices.cpp
new file mode 100644
index 000000000..197cfa220
--- /dev/null
+++ b/dom/media/MediaDevices.cpp
@@ -0,0 +1,300 @@
+/* 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 "mozilla/dom/MediaDevices.h"
+#include "mozilla/dom/MediaStreamBinding.h"
+#include "mozilla/dom/MediaDeviceInfo.h"
+#include "mozilla/dom/MediaDevicesBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/MediaManager.h"
+#include "MediaTrackConstraints.h"
+#include "nsIEventTarget.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIPermissionManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsQueryObject.h"
+
+#define DEVICECHANGE_HOLD_TIME_IN_MS 1000
+
+namespace mozilla {
+namespace dom {
+
+class FuzzTimerCallBack final : public nsITimerCallback
+{
+ ~FuzzTimerCallBack() {}
+
+public:
+ explicit FuzzTimerCallBack(MediaDevices* aMediaDevices) : mMediaDevices(aMediaDevices) {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Notify(nsITimer* aTimer) final
+ {
+ mMediaDevices->DispatchTrustedEvent(NS_LITERAL_STRING("devicechange"));
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<MediaDevices> mMediaDevices;
+};
+
+NS_IMPL_ISUPPORTS(FuzzTimerCallBack, nsITimerCallback)
+
+class MediaDevices::GumResolver : public nsIDOMGetUserMediaSuccessCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit GumResolver(Promise* aPromise) : mPromise(aPromise) {}
+
+ NS_IMETHOD
+ OnSuccess(nsISupports* aStream) override
+ {
+ RefPtr<DOMMediaStream> stream = do_QueryObject(aStream);
+ if (!stream) {
+ return NS_ERROR_FAILURE;
+ }
+ mPromise->MaybeResolve(stream);
+ return NS_OK;
+ }
+
+private:
+ virtual ~GumResolver() {}
+ RefPtr<Promise> mPromise;
+};
+
+class MediaDevices::EnumDevResolver : public nsIGetUserMediaDevicesSuccessCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ EnumDevResolver(Promise* aPromise, uint64_t aWindowId)
+ : mPromise(aPromise), mWindowId(aWindowId) {}
+
+ NS_IMETHOD
+ OnSuccess(nsIVariant* aDevices) override
+ {
+ // Cribbed from MediaPermissionGonk.cpp
+
+ // Create array for nsIMediaDevice
+ nsTArray<nsCOMPtr<nsIMediaDevice>> devices;
+ // Contain the fumes
+ {
+ uint16_t vtype;
+ nsresult rv = aDevices->GetDataType(&vtype);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (vtype != nsIDataType::VTYPE_EMPTY_ARRAY) {
+ nsIID elementIID;
+ uint16_t elementType;
+ void* rawArray;
+ uint32_t arrayLen;
+ rv = aDevices->GetAsArray(&elementType, &elementIID, &arrayLen, &rawArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (elementType != nsIDataType::VTYPE_INTERFACE) {
+ free(rawArray);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsISupports **supportsArray = reinterpret_cast<nsISupports **>(rawArray);
+ for (uint32_t i = 0; i < arrayLen; ++i) {
+ nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supportsArray[i]));
+ devices.AppendElement(device);
+ NS_IF_RELEASE(supportsArray[i]); // explicitly decrease refcount for rawptr
+ }
+ free(rawArray); // explicitly free memory from nsIVariant::GetAsArray
+ }
+ }
+ nsTArray<RefPtr<MediaDeviceInfo>> infos;
+ for (auto& device : devices) {
+ nsString type;
+ device->GetType(type);
+ bool isVideo = type.EqualsLiteral("video");
+ bool isAudio = type.EqualsLiteral("audio");
+ if (isVideo || isAudio) {
+ MediaDeviceKind kind = isVideo ?
+ MediaDeviceKind::Videoinput : MediaDeviceKind::Audioinput;
+ nsString id;
+ nsString name;
+ device->GetId(id);
+ // Include name only if page currently has a gUM stream active or
+ // persistent permissions (audio or video) have been granted
+ if (MediaManager::Get()->IsActivelyCapturingOrHasAPermission(mWindowId) ||
+ Preferences::GetBool("media.navigator.permission.disabled", false)) {
+ device->GetName(name);
+ }
+ RefPtr<MediaDeviceInfo> info = new MediaDeviceInfo(id, kind, name);
+ infos.AppendElement(info);
+ }
+ }
+ mPromise->MaybeResolve(infos);
+ return NS_OK;
+ }
+
+private:
+ virtual ~EnumDevResolver() {}
+ RefPtr<Promise> mPromise;
+ uint64_t mWindowId;
+};
+
+class MediaDevices::GumRejecter : public nsIDOMGetUserMediaErrorCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit GumRejecter(Promise* aPromise) : mPromise(aPromise) {}
+
+ NS_IMETHOD
+ OnError(nsISupports* aError) override
+ {
+ RefPtr<MediaStreamError> error = do_QueryObject(aError);
+ if (!error) {
+ return NS_ERROR_FAILURE;
+ }
+ mPromise->MaybeReject(error);
+ return NS_OK;
+ }
+
+private:
+ virtual ~GumRejecter() {}
+ RefPtr<Promise> mPromise;
+};
+
+MediaDevices::~MediaDevices()
+{
+ MediaManager* mediamanager = MediaManager::GetIfExists();
+ if (mediamanager) {
+ mediamanager->RemoveDeviceChangeCallback(this);
+ }
+}
+
+NS_IMPL_ISUPPORTS(MediaDevices::GumResolver, nsIDOMGetUserMediaSuccessCallback)
+NS_IMPL_ISUPPORTS(MediaDevices::EnumDevResolver, nsIGetUserMediaDevicesSuccessCallback)
+NS_IMPL_ISUPPORTS(MediaDevices::GumRejecter, nsIDOMGetUserMediaErrorCallback)
+
+already_AddRefed<Promise>
+MediaDevices::GetUserMedia(const MediaStreamConstraints& aConstraints,
+ ErrorResult &aRv)
+{
+ nsPIDOMWindowInner* window = GetOwner();
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
+ RefPtr<Promise> p = Promise::Create(go, aRv);
+ NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+ RefPtr<GumResolver> resolver = new GumResolver(p);
+ RefPtr<GumRejecter> rejecter = new GumRejecter(p);
+
+ aRv = MediaManager::Get()->GetUserMedia(window, aConstraints,
+ resolver, rejecter);
+ return p.forget();
+}
+
+already_AddRefed<Promise>
+MediaDevices::EnumerateDevices(ErrorResult &aRv)
+{
+ nsPIDOMWindowInner* window = GetOwner();
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
+ RefPtr<Promise> p = Promise::Create(go, aRv);
+ NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+ RefPtr<EnumDevResolver> resolver = new EnumDevResolver(p, window->WindowID());
+ RefPtr<GumRejecter> rejecter = new GumRejecter(p);
+
+ aRv = MediaManager::Get()->EnumerateDevices(window, resolver, rejecter);
+ return p.forget();
+}
+
+NS_IMPL_ADDREF_INHERITED(MediaDevices, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(MediaDevices, DOMEventTargetHelper)
+NS_INTERFACE_MAP_BEGIN(MediaDevices)
+ NS_INTERFACE_MAP_ENTRY(MediaDevices)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+void
+MediaDevices::OnDeviceChange()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ if (!(MediaManager::Get()->IsActivelyCapturingOrHasAPermission(GetOwner()->WindowID()) ||
+ Preferences::GetBool("media.navigator.permission.disabled", false))) {
+ return;
+ }
+
+ if (!mFuzzTimer)
+ {
+ mFuzzTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+
+ if (!mFuzzTimer) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ mFuzzTimer->Cancel();
+ RefPtr<FuzzTimerCallBack> cb = new FuzzTimerCallBack(this);
+ mFuzzTimer->InitWithCallback(cb, DEVICECHANGE_HOLD_TIME_IN_MS, nsITimer::TYPE_ONE_SHOT);
+}
+
+mozilla::dom::EventHandlerNonNull*
+MediaDevices::GetOndevicechange()
+{
+ if (NS_IsMainThread()) {
+ return GetEventHandler(nsGkAtoms::ondevicechange, EmptyString());
+ }
+ return GetEventHandler(nullptr, NS_LITERAL_STRING("devicechange"));
+}
+
+void
+MediaDevices::SetOndevicechange(mozilla::dom::EventHandlerNonNull* aCallback)
+{
+ if (NS_IsMainThread()) {
+ SetEventHandler(nsGkAtoms::ondevicechange, EmptyString(), aCallback);
+ } else {
+ SetEventHandler(nullptr, NS_LITERAL_STRING("devicechange"), aCallback);
+ }
+
+ MediaManager::Get()->AddDeviceChangeCallback(this);
+}
+
+nsresult
+MediaDevices::AddEventListener(const nsAString& aType,
+ nsIDOMEventListener* aListener,
+ bool aUseCapture, bool aWantsUntrusted,
+ uint8_t optional_argc)
+{
+ MediaManager::Get()->AddDeviceChangeCallback(this);
+
+ return mozilla::DOMEventTargetHelper::AddEventListener(aType, aListener,
+ aUseCapture,
+ aWantsUntrusted,
+ optional_argc);
+}
+
+void
+MediaDevices::AddEventListener(const nsAString& aType,
+ dom::EventListener* aListener,
+ const dom::AddEventListenerOptionsOrBoolean& aOptions,
+ const dom::Nullable<bool>& aWantsUntrusted,
+ ErrorResult& aRv)
+{
+ MediaManager::Get()->AddDeviceChangeCallback(this);
+
+ return mozilla::DOMEventTargetHelper::AddEventListener(aType, aListener,
+ aOptions,
+ aWantsUntrusted,
+ aRv);
+}
+
+JSObject*
+MediaDevices::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaDevicesBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla