--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1951,16 +1951,19 @@ GK_ATOM(onuserproximity, "onuserproximit
// light sensor support
GK_ATOM(ondevicelight, "ondevicelight")
// Audio channel events
GK_ATOM(onmozinterruptbegin, "onmozinterruptbegin")
GK_ATOM(onmozinterruptend, "onmozinterruptend")
+// MediaDevices device change event
+GK_ATOM(ondevicechange, "ondevicechange")
+
//---------------------------------------------------------------------------
// Special atoms
//---------------------------------------------------------------------------
// Node types
GK_ATOM(cdataTagName, "#cdata-section")
GK_ATOM(commentTagName, "#comment")
GK_ATOM(documentNodeName, "#document")
--- a/dom/media/MediaDevices.cpp
+++ b/dom/media/MediaDevices.cpp
@@ -174,16 +174,82 @@ MediaDevices::EnumerateDevices(ErrorResu
}
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))
+ return;
+
+ if (!(MediaManager::Get()->IsActivelyCapturingOrHasAPermission(GetOwner()->WindowID()) ||
+ Preferences::GetBool("media.navigator.permission.disabled", false))) {
+ return;
+ }
+
+ DispatchTrustedEvent(NS_LITERAL_STRING("devicechange"));
+}
+
+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
--- a/dom/media/MediaDevices.h
+++ b/dom/media/MediaDevices.h
@@ -5,29 +5,31 @@
#ifndef mozilla_dom_MediaDevices_h
#define mozilla_dom_MediaDevices_h
#include "mozilla/ErrorResult.h"
#include "nsISupportsImpl.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/BindingUtils.h"
#include "nsPIDOMWindow.h"
+#include "DeviceChangeCallback.h"
namespace mozilla {
namespace dom {
class Promise;
struct MediaStreamConstraints;
struct MediaTrackSupportedConstraints;
#define MOZILLA_DOM_MEDIADEVICES_IMPLEMENTATION_IID \
{ 0x2f784d8a, 0x7485, 0x4280, \
{ 0x9a, 0x36, 0x74, 0xa4, 0xd6, 0x71, 0xa6, 0xc8 } }
class MediaDevices final : public DOMEventTargetHelper
+ ,public DeviceChangeCallback
{
public:
explicit MediaDevices(nsPIDOMWindowInner* aWindow) :
DOMEventTargetHelper(aWindow) {}
NS_DECL_ISUPPORTS_INHERITED
NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_MEDIADEVICES_IMPLEMENTATION_IID)
@@ -37,16 +39,33 @@ public:
void GetSupportedConstraints(MediaTrackSupportedConstraints& aResult) {};
already_AddRefed<Promise>
GetUserMedia(const MediaStreamConstraints& aConstraints, ErrorResult &aRv);
already_AddRefed<Promise>
EnumerateDevices(ErrorResult &aRv);
+ virtual void OnDeviceChange() override;
+
+ mozilla::dom::EventHandlerNonNull* GetOndevicechange();
+
+ void SetOndevicechange(mozilla::dom::EventHandlerNonNull* aCallback);
+
+ NS_IMETHOD AddEventListener(const nsAString& aType,
+ nsIDOMEventListener* aListener,
+ bool aUseCapture, bool aWantsUntrusted,
+ uint8_t optional_argc) override;
+
+ virtual void AddEventListener(const nsAString& aType,
+ dom::EventListener* aListener,
+ const dom::AddEventListenerOptionsOrBoolean& aOptions,
+ const dom::Nullable<bool>& aWantsUntrusted,
+ ErrorResult& aRv) override;
+
private:
class GumResolver;
class EnumDevResolver;
class GumRejecter;
virtual ~MediaDevices() {}
};
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -39,16 +39,17 @@
#include "mozilla/Types.h"
#include "mozilla/PeerIdentity.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/MediaStreamBinding.h"
#include "mozilla/dom/MediaStreamTrackBinding.h"
#include "mozilla/dom/GetUserMediaRequestBinding.h"
#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/MediaDevices.h"
#include "mozilla/Base64.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/media/MediaChild.h"
#include "mozilla/media/MediaTaskUtils.h"
#include "MediaTrackConstraints.h"
#include "VideoUtils.h"
#include "Latency.h"
#include "nsProxyRelease.h"
@@ -1991,16 +1992,35 @@ bool MediaManager::IsLoop(nsIURI* aDocUR
bool MediaManager::IsPrivateBrowsing(nsPIDOMWindowInner* window)
{
nsCOMPtr<nsIDocument> doc = window->GetDoc();
nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
return loadContext && loadContext->UsePrivateBrowsing();
}
+int MediaManager::AddDeviceChangeCallback(DeviceChangeCallback* aCallback)
+{
+ MediaManager::PostTask(NewTaskFrom([]() {
+ RefPtr<MediaManager> manager = MediaManager_GetInstance();
+ manager->GetBackend(0)->AddDeviceChangeCallback(manager);
+ }));
+
+ return DeviceChangeCallback::AddDeviceChangeCallback(aCallback);
+}
+
+void MediaManager::OnDeviceChange() {
+ RefPtr<MediaManager> self(this);
+ NS_DispatchToMainThread(media::NewRunnableFrom([self,this]() mutable {
+ MOZ_ASSERT(NS_IsMainThread());
+ DeviceChangeCallback::OnDeviceChange();
+ return NS_OK;
+ }));
+}
+
nsresult MediaManager::GenerateUUID(nsAString& aResult)
{
nsresult rv;
nsCOMPtr<nsIUUIDGenerator> uuidgen =
do_GetService("@mozilla.org/uuid-generator;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Generate a call ID.
@@ -2720,16 +2740,35 @@ MediaManager::OnNavigation(uint64_t aWin
// This is safe since we're on main-thread, and the windowlist can only
// be added to from the main-thread
auto* window = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
if (window) {
IterateWindowListeners(window->AsInner(), StopSharingCallback, nullptr);
} else {
RemoveWindowID(aWindowID);
}
+
+ RemoveMediaDevicesCallback(aWindowID);
+}
+
+void
+MediaManager::RemoveMediaDevicesCallback(uint64_t aWindowID)
+{
+ for (DeviceChangeCallback* observer : mDeviceChangeCallbackList)
+ {
+ dom::MediaDevices* mediadevices = static_cast<dom::MediaDevices *>(observer);
+ MOZ_ASSERT(mediadevices);
+ if (mediadevices) {
+ nsPIDOMWindowInner* window = mediadevices->GetOwner();
+ MOZ_ASSERT(window);
+ if (window && window->WindowID() == aWindowID)
+ DeviceChangeCallback::RemoveDeviceChangeCallback(observer);
+ return;
+ }
+ }
}
StreamListeners*
MediaManager::AddWindowID(uint64_t aWindowId)
{
MOZ_ASSERT(NS_IsMainThread());
// Store the WindowID in a hash table and mark as active. The entry is removed
// when this window is closed or navigated away from.
@@ -2899,16 +2938,17 @@ MediaManager::Shutdown()
Run() override
{
LOG(("MediaManager Thread Shutdown"));
MOZ_ASSERT(MediaManager::IsInMediaThread());
// Must shutdown backend on MediaManager thread, since that's where we started it from!
{
if (mManager->mBackend) {
mManager->mBackend->Shutdown(); // ok to invoke multiple times
+ mManager->mBackend->RemoveDeviceChangeCallback(mManager);
}
}
mozilla::ipc::BackgroundChild::CloseForCurrentThread();
// must explicitly do this before dispatching the reply, since the reply may kill us with Stop()
mManager->mBackend = nullptr; // last reference, will invoke Shutdown() again
if (NS_FAILED(NS_DispatchToMainThread(mReply.forget()))) {
LOG(("Will leak thread: DispatchToMainthread of reply runnable failed in MediaManager shutdown"));
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -1,16 +1,17 @@
/* 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/. */
#ifndef MOZILLA_MEDIAMANAGER_H
#define MOZILLA_MEDIAMANAGER_H
#include "MediaEngine.h"
+#include "DeviceChangeCallback.h"
#include "mozilla/Services.h"
#include "mozilla/unused.h"
#include "nsAutoPtr.h"
#include "nsIMediaManager.h"
#include "nsHashKeys.h"
#include "nsGlobalWindow.h"
#include "nsClassHashtable.h"
@@ -182,16 +183,17 @@ typedef nsClassHashtable<nsUint64HashKey
// we could add MediaManager if needed
typedef void (*WindowListenerCallback)(MediaManager *aThis,
uint64_t aWindowID,
StreamListeners *aListeners,
void *aData);
class MediaManager final : public nsIMediaManagerService,
public nsIObserver
+ ,public DeviceChangeCallback
{
friend GetUserMediaCallbackMediaStreamListener;
public:
static already_AddRefed<MediaManager> GetInstance();
// NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
// thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
// from MediaManager thread.
@@ -251,16 +253,19 @@ public:
nsresult EnumerateDevices(nsPIDOMWindowInner* aWindow, dom::Promise& aPromise);
void OnNavigation(uint64_t aWindowID);
bool IsActivelyCapturingOrHasAPermission(uint64_t aWindowId);
MediaEnginePrefs mPrefs;
typedef nsTArray<RefPtr<MediaDevice>> SourceSet;
static bool IsPrivateBrowsing(nsPIDOMWindowInner* window);
+
+ virtual int AddDeviceChangeCallback(DeviceChangeCallback* aCallback) override;
+ virtual void OnDeviceChange() override;
private:
typedef media::Pledge<SourceSet*, dom::MediaStreamError*> PledgeSourceSet;
typedef media::Pledge<const char*, dom::MediaStreamError*> PledgeChar;
typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
static bool IsPrivileged();
static bool IsLoop(nsIURI* aDocURI);
static nsresult GenerateUUID(nsAString& aResult);
@@ -303,16 +308,17 @@ private:
void Shutdown();
void StopScreensharing(uint64_t aWindowID);
void IterateWindowListeners(nsPIDOMWindowInner *aWindow,
WindowListenerCallback aCallback,
void *aData);
void StopMediaStreams();
+ void RemoveMediaDevicesCallback(uint64_t aWindowID);
// ONLY access from MainThread so we don't need to lock
WindowTable mActiveWindows;
nsRefPtrHashtable<nsStringHashKey, GetUserMediaTask> mActiveCallbacks;
nsClassHashtable<nsUint64HashKey, nsTArray<nsString>> mCallIds;
// Always exists
nsAutoPtr<base::Thread> mMediaThread;
--- a/dom/media/systemservices/CamerasChild.cpp
+++ b/dom/media/systemservices/CamerasChild.cpp
@@ -103,16 +103,22 @@ GetCamerasChild() {
CamerasSingleton::Child() = runnable->GetCamerasChild();
}
if (!CamerasSingleton::Child()) {
LOG(("Failed to set up CamerasChild, are we in shutdown?"));
}
return CamerasSingleton::Child();
}
+CamerasChild*
+GetCamerasChildIfExists() {
+ OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
+ return CamerasSingleton::Child();
+}
+
bool
CamerasChild::RecvReplyFailure(void)
{
LOG((__PRETTY_FUNCTION__));
MonitorAutoLock monitor(mReplyMonitor);
mReceivedReply = true;
mReplySuccess = false;
monitor.Notify();
@@ -575,16 +581,23 @@ CamerasChild::RecvDeliverFrame(const int
} else {
LOG(("DeliverFrame called with dead callback"));
}
SendReleaseFrame(shmem);
return true;
}
bool
+CamerasChild::RecvDeviceChange()
+{
+ this->OnDeviceChange();
+ return true;
+}
+
+bool
CamerasChild::RecvFrameSizeChange(const int& capEngine,
const int& capId,
const int& w, const int& h)
{
LOG((__PRETTY_FUNCTION__));
MutexAutoLock lock(mCallbackMutex);
CaptureEngine capEng = static_cast<CaptureEngine>(capEngine);
if (Callback(capEng, capId)) {
--- a/dom/media/systemservices/CamerasChild.h
+++ b/dom/media/systemservices/CamerasChild.h
@@ -7,16 +7,17 @@
#ifndef mozilla_CamerasChild_h
#define mozilla_CamerasChild_h
#include "mozilla/Move.h"
#include "mozilla/Pair.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/camera/PCamerasChild.h"
#include "mozilla/camera/PCamerasParent.h"
+#include "DeviceChangeCallback.h"
#include "mozilla/Mutex.h"
#include "base/singleton.h"
#include "nsCOMPtr.h"
// conflicts with #include of scoped_ptr.h
#undef FF
#include "webrtc/common.h"
// Video Engine
@@ -113,16 +114,18 @@ private:
// Get a pointer to a CamerasChild object we can use to do IPC with.
// This does everything needed to set up, including starting the IPC
// channel with PBackground, blocking until thats done, and starting the
// thread to do IPC on. This will fail if we're in shutdown. On success
// it will set up the CamerasSingleton.
CamerasChild* GetCamerasChild();
+CamerasChild* GetCamerasChildIfExists();
+
// Shut down the IPC channel and everything associated, like WebRTC.
// This is a static call because the CamerasChild object may not even
// be alive when we're called.
void Shutdown(void);
// Obtain the CamerasChild object (if possible, i.e. not shutting down),
// and maintain a grip on the object for the duration of the call.
template <class MEM_FUN, class... ARGS>
@@ -133,16 +136,17 @@ int GetChildAndCall(MEM_FUN&& f, ARGS&&.
if (child) {
return (child->*f)(mozilla::Forward<ARGS>(args)...);
} else {
return -1;
}
}
class CamerasChild final : public PCamerasChild
+ ,public DeviceChangeCallback
{
friend class mozilla::ipc::BackgroundChildImpl;
template <class T> friend class mozilla::camera::LockAndDispatch;
public:
// We are owned by the PBackground thread only. CamerasSingleton
// takes a non-owning reference.
NS_INLINE_DECL_REFCOUNTING(CamerasChild)
@@ -150,16 +154,18 @@ public:
// IPC messages recevied, received on the PBackground thread
// these are the actual callbacks with data
virtual bool RecvDeliverFrame(const int&, const int&, mozilla::ipc::Shmem&&,
const size_t&, const uint32_t&, const int64_t&,
const int64_t&) override;
virtual bool RecvFrameSizeChange(const int&, const int&,
const int& w, const int& h) override;
+ virtual bool RecvDeviceChange() override;
+
// these are response messages to our outgoing requests
virtual bool RecvReplyNumberOfCaptureDevices(const int&) override;
virtual bool RecvReplyNumberOfCapabilities(const int&) override;
virtual bool RecvReplyAllocateCaptureDevice(const int&) override;
virtual bool RecvReplyGetCaptureCapability(const CaptureCapability& capability) override;
virtual bool RecvReplyGetCaptureDevice(const nsCString& device_name,
const nsCString& device_id) override;
virtual bool RecvReplyFailure(void) override;
--- a/dom/media/systemservices/CamerasParent.cpp
+++ b/dom/media/systemservices/CamerasParent.cpp
@@ -34,16 +34,34 @@ namespace camera {
// - the main thread for some setups, and occassionally for video capture setup
// calls that don't work correctly elsewhere.
// - the IPC thread on which PBackground is running and which receives and
// sends messages
// - a thread which will execute the actual (possibly slow) camera access
// called "VideoCapture". On Windows this is a thread with an event loop
// suitable for UI access.
+void InputObserver::DeviceChange() {
+ LOG((__PRETTY_FUNCTION__));
+ MOZ_ASSERT(mParent);
+
+ RefPtr<nsIRunnable> ipc_runnable =
+ media::NewRunnableFrom([this]() -> nsresult {
+ if (mParent->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+ Unused << mParent->SendDeviceChange();
+ return NS_OK;
+ });
+
+ nsIThread* thread = mParent->GetBackgroundThread();
+ MOZ_ASSERT(thread != nullptr);
+ thread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
+};
+
class FrameSizeChangeRunnable : public Runnable {
public:
FrameSizeChangeRunnable(CamerasParent *aParent, CaptureEngine capEngine,
int cap_id, unsigned int aWidth, unsigned int aHeight)
: mParent(aParent), mCapEngine(capEngine), mCapId(cap_id),
mWidth(aWidth), mHeight(aHeight) {}
NS_IMETHOD Run() override {
@@ -394,16 +412,25 @@ CamerasParent::SetupEngine(CaptureEngine
}
helper->mPtrViECapture = webrtc::ViECapture::GetInterface(helper->mEngine);
if (!helper->mPtrViECapture) {
LOG(("ViECapture::GetInterface failed"));
return false;
}
+ InputObserver** observer = mObservers.AppendElement(
+ new InputObserver(this));
+
+#ifdef DEBUG
+ MOZ_ASSERT(0 == helper->mPtrViECapture->RegisterInputObserver(*observer));
+#else
+ helper->mPtrViECapture->RegisterInputObserver(*observer);
+#endif
+
helper->mPtrViERender = webrtc::ViERender::GetInterface(helper->mEngine);
if (!helper->mPtrViERender) {
LOG(("ViERender::GetInterface failed"));
return false;
}
return true;
}
@@ -430,30 +457,41 @@ CamerasParent::CloseEngines()
if (mEngines[i].mEngineIsRunning) {
LOG(("Being closed down while engine %d is running!", i));
}
if (mEngines[i].mPtrViERender) {
mEngines[i].mPtrViERender->Release();
mEngines[i].mPtrViERender = nullptr;
}
if (mEngines[i].mPtrViECapture) {
+#ifdef DEBUG
+ MOZ_ASSERT(0 == mEngines[i].mPtrViECapture->DeregisterInputObserver());
+#else
+ mEngines[i].mPtrViECapture->DeregisterInputObserver();
+#endif
+
mEngines[i].mPtrViECapture->Release();
mEngines[i].mPtrViECapture = nullptr;
}
if(mEngines[i].mPtrViEBase) {
mEngines[i].mPtrViEBase->Release();
mEngines[i].mPtrViEBase = nullptr;
}
if (mEngines[i].mEngine) {
mEngines[i].mEngine->SetTraceCallback(nullptr);
webrtc::VideoEngine::Delete(mEngines[i].mEngine);
mEngines[i].mEngine = nullptr;
}
}
+ for (InputObserver* observer : mObservers) {
+ delete observer;
+ }
+ mObservers.Clear();
+
mWebRTCAlive = false;
}
bool
CamerasParent::EnsureInitialized(int aEngine)
{
LOG((__PRETTY_FUNCTION__));
// We're shutting down, don't try to do new WebRTC ops.
--- a/dom/media/systemservices/CamerasParent.h
+++ b/dom/media/systemservices/CamerasParent.h
@@ -71,16 +71,29 @@ public:
// The webrtc code keeps a reference to this one.
webrtc::Config mConfig;
// Engine alive
bool mEngineIsRunning;
};
+class InputObserver : public webrtc::ViEInputObserver
+{
+public:
+ explicit InputObserver(CamerasParent* aParent)
+ : mParent(aParent) {};
+ virtual void DeviceChange();
+
+ friend CamerasParent;
+
+private:
+ RefPtr<CamerasParent> mParent;
+};
+
class CamerasParent : public PCamerasParent,
public nsIObserver
{
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
public:
static already_AddRefed<CamerasParent> Create();
@@ -148,16 +161,17 @@ protected:
base::Thread* mVideoCaptureThread;
// Shutdown handling
bool mChildIsAlive;
bool mDestroyed;
// Above 2 are PBackground only, but this is potentially
// read cross-thread.
mozilla::Atomic<bool> mWebRTCAlive;
+ nsTArray<InputObserver*> mObservers;
};
PCamerasParent* CreateCamerasParent();
} // namespace camera
} // namespace mozilla
#endif // mozilla_CameraParent_h
new file mode 100644
--- /dev/null
+++ b/dom/media/systemservices/DeviceChangeCallback.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* 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/. */
+
+#ifndef mozilla_DeviceChangeCallback_h
+#define mozilla_DeviceChangeCallback_h
+
+namespace mozilla {
+
+class DeviceChangeCallback
+{
+public:
+ virtual void OnDeviceChange()
+ {
+ MutexAutoLock lock(mCallbackMutex);
+ for (DeviceChangeCallback* observer : mDeviceChangeCallbackList)
+ {
+ observer->OnDeviceChange();
+ }
+ }
+
+ virtual int AddDeviceChangeCallback(DeviceChangeCallback* aCallback)
+ {
+ MutexAutoLock lock(mCallbackMutex);
+ if (mDeviceChangeCallbackList.IndexOf(aCallback) == mDeviceChangeCallbackList.NoIndex)
+ mDeviceChangeCallbackList.AppendElement(aCallback);
+ return 0;
+ }
+
+ virtual int RemoveDeviceChangeCallback(DeviceChangeCallback* aCallback)
+ {
+ MutexAutoLock lock(mCallbackMutex);
+ if (mDeviceChangeCallbackList.IndexOf(aCallback) != mDeviceChangeCallbackList.NoIndex)
+ mDeviceChangeCallbackList.RemoveElement(aCallback);
+ return 0;
+ }
+
+ DeviceChangeCallback() : mCallbackMutex("mozilla::media::DeviceChangeCallback::mCallbackMutex")
+ {
+ mDeviceChangeCallbackList.Clear();
+ }
+
+ virtual ~DeviceChangeCallback()
+ {
+ mDeviceChangeCallbackList.Clear();
+ }
+
+protected:
+ nsTArray<DeviceChangeCallback*> mDeviceChangeCallbackList;
+ Mutex mCallbackMutex;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_DeviceChangeCallback_h
--- a/dom/media/systemservices/PCameras.ipdl
+++ b/dom/media/systemservices/PCameras.ipdl
@@ -24,16 +24,17 @@ async protocol PCameras
manager PBackground;
child:
async FrameSizeChange(int capEngine, int cap_id, int w, int h);
// transfers ownership of |buffer| from parent to child
async DeliverFrame(int capEngine, int cap_id,
Shmem buffer, size_t size, uint32_t time_stamp,
int64_t ntp_time, int64_t render_time);
+ async DeviceChange();
async ReplyNumberOfCaptureDevices(int numdev);
async ReplyNumberOfCapabilities(int numdev);
async ReplyAllocateCaptureDevice(int numdev);
async ReplyGetCaptureCapability(CaptureCapability cap);
async ReplyGetCaptureDevice(nsCString device_name, nsCString device_id);
async ReplyFailure();
async ReplySuccess();
async __delete__();
--- a/dom/media/systemservices/moz.build
+++ b/dom/media/systemservices/moz.build
@@ -3,16 +3,17 @@
# 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/.
if CONFIG['MOZ_WEBRTC']:
EXPORTS += [
'CamerasChild.h',
'CamerasParent.h',
+ 'DeviceChangeCallback.h',
'LoadManager.h',
'LoadManagerFactory.h',
'LoadMonitor.h',
]
UNIFIED_SOURCES += [
'CamerasChild.cpp',
'CamerasParent.cpp',
'LoadManager.cpp',
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -6,16 +6,17 @@
#define MEDIAENGINE_H_
#include "mozilla/RefPtr.h"
#include "DOMMediaStream.h"
#include "MediaStreamGraph.h"
#include "MediaTrackConstraints.h"
#include "mozilla/dom/MediaStreamTrackBinding.h"
#include "mozilla/dom/VideoStreamTrack.h"
+#include "DeviceChangeCallback.h"
namespace mozilla {
namespace dom {
class Blob;
} // namespace dom
enum {
@@ -36,17 +37,17 @@ class MediaEngineAudioSource;
enum MediaEngineState {
kAllocated,
kStarted,
kStopped,
kReleased
};
-class MediaEngine
+class MediaEngine : public DeviceChangeCallback
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEngine)
static const int DEFAULT_VIDEO_FPS = 30;
static const int DEFAULT_VIDEO_MIN_FPS = 10;
static const int DEFAULT_43_VIDEO_WIDTH = 640;
static const int DEFAULT_43_VIDEO_HEIGHT = 480;
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -120,16 +120,20 @@ MediaEngineWebRTC::MediaEngineWebRTC(Med
}
#else
#ifdef MOZ_WIDGET_GONK
AsyncLatencyLogger::Get()->AddRef();
#endif
#endif
// XXX
gFarendObserver = new AudioOutputObserver();
+
+ camera::GetChildAndCall(
+ &camera::CamerasChild::AddDeviceChangeCallback,
+ this);
}
void
MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
nsTArray<RefPtr<MediaEngineVideoSource> >* aVSources)
{
// We spawn threads to handle gUM runnables, so we must protect the member vars
MutexAutoLock lock(mMutex);
@@ -415,16 +419,21 @@ MediaEngineWebRTC::EnumerateAudioDevices
}
void
MediaEngineWebRTC::Shutdown()
{
// This is likely paranoia
MutexAutoLock lock(mMutex);
+ if (camera::GetCamerasChildIfExists()) {
+ camera::GetChildAndCall(
+ &camera::CamerasChild::RemoveDeviceChangeCallback, this);
+ }
+
LOG(("%s", __FUNCTION__));
// Shutdown all the sources, since we may have dangling references to the
// sources in nsDOMUserMediaStreams waiting for GC/CC
for (auto iter = mVideoSources.Iter(); !iter.Done(); iter.Next()) {
MediaEngineVideoSource* source = iter.UserData();
if (source) {
source->Shutdown();
}
--- a/dom/webidl/MediaDevices.webidl
+++ b/dom/webidl/MediaDevices.webidl
@@ -7,17 +7,18 @@
* http://dev.w3.org/2011/webrtc/editor/getusermedia.html
*
* Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
[Func="Navigator::HasUserMediaSupport"]
interface MediaDevices : EventTarget {
-// attribute EventHandler ondevicechange;
+ [Pref="media.ondevicechange.enabled"]
+ attribute EventHandler ondevicechange;
MediaTrackSupportedConstraints getSupportedConstraints();
[Throws]
Promise<sequence<MediaDeviceInfo>> enumerateDevices();
[Throws]
Promise<MediaStream> getUserMedia(optional MediaStreamConstraints constraints);
};
--- a/media/webrtc/trunk/webrtc/modules/video_capture/include/video_capture.h
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/include/video_capture.h
@@ -16,27 +16,45 @@
#include "webrtc/modules/video_capture/include/video_capture_defines.h"
#if defined(ANDROID) && !defined(WEBRTC_GONK)
#include <jni.h>
#endif
namespace webrtc {
+class VideoInputFeedBack
+{
+public:
+ virtual void OnDeviceChange() = 0;
+protected:
+ virtual ~VideoInputFeedBack(){}
+};
+
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) && !defined(WEBRTC_GONK)
int32_t SetCaptureAndroidVM(JavaVM* javaVM);
#endif
class VideoCaptureModule: public RefCountedModule {
public:
// Interface for receiving information about available camera devices.
class DeviceInfo {
public:
virtual uint32_t NumberOfDevices() = 0;
virtual int32_t Refresh() = 0;
+ virtual void DeviceChange() {
+ if (_inputCallBack)
+ _inputCallBack->OnDeviceChange();
+ }
+ virtual void RegisterVideoInputFeedBack(VideoInputFeedBack& callBack) {
+ _inputCallBack = &callBack;
+ }
+ virtual void DeRegisterVideoInputFeedBack() {
+ _inputCallBack = NULL;
+ }
// Returns the available capture devices.
// deviceNumber - Index of capture device.
// deviceNameUTF8 - Friendly name of the capture device.
// deviceUniqueIdUTF8 - Unique name of the capture device if it exist.
// Otherwise same as deviceNameUTF8.
// productUniqueIdUTF8 - Unique product id if it exist.
// Null terminated otherwise.
@@ -77,16 +95,18 @@ class VideoCaptureModule: public RefCoun
virtual int32_t DisplayCaptureSettingsDialogBox(
const char* deviceUniqueIdUTF8,
const char* dialogTitleUTF8,
void* parentWindow,
uint32_t positionX,
uint32_t positionY) = 0;
virtual ~DeviceInfo() {}
+ private:
+ VideoInputFeedBack* _inputCallBack = NULL;
};
class VideoCaptureEncodeInterface {
public:
virtual int32_t ConfigureEncoder(const VideoCodec& codec,
uint32_t maxPayloadSize) = 0;
// Inform the encoder about the new target bit rate.
// - newBitRate : New target bit rate in Kbit/s.
--- a/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info.mm
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info.mm
@@ -19,21 +19,23 @@ namespace webrtc
namespace videocapturemodule
{
VideoCaptureMacAVFoundationInfo::VideoCaptureMacAVFoundationInfo(const int32_t id) :
DeviceInfoImpl(id)
{
nsAutoreleasePool localPool;
_captureInfo = [[VideoCaptureMacAVFoundationInfoObjC alloc] init];
+ [_captureInfo registerOwner:this];
}
VideoCaptureMacAVFoundationInfo::~VideoCaptureMacAVFoundationInfo()
{
nsAutoreleasePool localPool;
+ [_captureInfo registerOwner:nil];
[_captureInfo release];
}
int32_t VideoCaptureMacAVFoundationInfo::Init()
{
return 0;
}
--- a/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info_objc.h
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info_objc.h
@@ -21,16 +21,19 @@
#include "webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info.h"
#include "webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_utility.h"
@interface VideoCaptureMacAVFoundationInfoObjC : NSObject{
bool _OSSupportedInfo;
NSArray* _captureDevicesInfo;
int _captureDeviceCountInfo;
+ NSArray* _observers;
+ NSLock* _lock;
+ webrtc::videocapturemodule::VideoCaptureMacAVFoundationInfo* _owner;
}
/**************************************************************************
*
* The following functions are considered to be private
*
***************************************************************************/
@@ -41,16 +44,18 @@
/**************************************************************************
*
* The following functions are considered to be public and called by VideoCaptureMacAVFoundationInfo class
*
***************************************************************************/
+- (void)registerOwner:(webrtc::videocapturemodule::VideoCaptureMacAVFoundationInfo*)owner;
+
- (NSNumber*)getCaptureDeviceCount;
- (NSNumber*)getCaptureCapabilityCount:(const char*)uniqueId;
- (NSNumber*)getCaptureCapability:(const char*)uniqueId
CapabilityId:(uint32_t)capabilityId
Capability_width:(int32_t*)width
Capability_height:(int32_t*)height
--- a/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info_objc.mm
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info_objc.mm
@@ -10,16 +10,17 @@
#pragma mark **** imports/includes
#import "webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info_objc.h"
#include "webrtc/system_wrappers/interface/trace.h"
using namespace webrtc;
+using namespace videocapturemodule;
#pragma mark **** hidden class interface
@implementation VideoCaptureMacAVFoundationInfoObjC
// ****************** over-written OS methods ***********************
#pragma mark **** over-written OS methods
@@ -33,22 +34,35 @@ using namespace webrtc;
}
else
{
return nil;
}
return self;
}
+- (void)registerOwner:(VideoCaptureMacAVFoundationInfo*)owner {
+ [_lock lock];
+ _owner = owner;
+ [_lock unlock];
+}
+
/// ***** Objective-C. Similar to C++ destructor
/// ***** Returns nothing
- (void)dealloc {
[_captureDevicesInfo release];
+ // Remove Observers
+ NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
+ for (id observer in _observers)
+ [notificationCenter removeObserver:observer];
+ [_observers release];
+ [_lock release];
+
[super dealloc];
}
// ****************** public methods ******************
#pragma mark **** public method implementations
/// ***** Creates a message box with Cocoa framework
/// ***** Returns 0 on success, -1 otherwise.
@@ -218,16 +232,43 @@ using namespace webrtc;
if(NO == _OSSupportedInfo)
{
return [NSNumber numberWithInt:0];
}
_captureDeviceCountInfo = 0;
[self getCaptureDevices];
+ _lock = [[NSLock alloc] init];
+
+ //register device connected / disconnected event
+ NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
+
+ id deviceWasConnectedObserver = [notificationCenter addObserverForName:AVCaptureDeviceWasConnectedNotification
+ object:nil
+ queue:[NSOperationQueue mainQueue]
+ usingBlock:^(NSNotification *note) {
+ [_lock lock];
+ if(_owner)
+ _owner->DeviceChange();
+ [_lock unlock];
+ }];
+
+ id deviceWasDisconnectedObserver = [notificationCenter addObserverForName:AVCaptureDeviceWasDisconnectedNotification
+ object:nil
+ queue:[NSOperationQueue mainQueue]
+ usingBlock:^(NSNotification *note) {
+ [_lock lock];
+ if(_owner)
+ _owner->DeviceChange();
+ [_lock unlock];
+ }];
+
+ _observers = [[NSArray alloc] initWithObjects:deviceWasConnectedObserver, deviceWasDisconnectedObserver, nil];
+
return [NSNumber numberWithInt:0];
}
// ***** Checks to see if the AVCaptureSession framework is available in the OS
// ***** If it is not, isOSSupprted = NO
// ***** Throughout the rest of the class isOSSupprted is checked and functions
// ***** are/aren't called depending
// ***** The user can use weak linking to the AVFoundation framework and run on older
--- a/media/webrtc/trunk/webrtc/video_engine/include/vie_capture.h
+++ b/media/webrtc/trunk/webrtc/video_engine/include/vie_capture.h
@@ -22,16 +22,27 @@
#include "webrtc/common_video/interface/i420_video_frame.h"
#include "webrtc/common_video/rotation.h"
namespace webrtc {
class VideoEngine;
class VideoCaptureModule;
+// The observer is registered using RegisterInputObserver() and
+// deregistered using DeregisterInputObserver().
+class WEBRTC_DLLEXPORT ViEInputObserver {
+ public:
+ // This method is called if an input device is connected or disconnected .
+ virtual void DeviceChange() = 0;
+
+ protected:
+ virtual ~ViEInputObserver() {}
+};
+
// This structure describes one set of the supported capabilities for a capture
// device.
struct CaptureCapability {
unsigned int width;
unsigned int height;
unsigned int maxFPS;
RawVideoType rawType;
VideoCodecType codecType;
@@ -211,19 +222,23 @@ class WEBRTC_DLLEXPORT ViECapture {
// Enables brightness alarm detection and the brightness alarm callback.
virtual int EnableBrightnessAlarm(const int capture_id,
const bool enable) = 0;
// Registers an instance of a user implementation of the ViECaptureObserver.
virtual int RegisterObserver(const int capture_id,
ViECaptureObserver& observer) = 0;
+ virtual int RegisterInputObserver(ViEInputObserver* observer) = 0;
+
// Removes an already registered instance of ViECaptureObserver.
virtual int DeregisterObserver(const int capture_id) = 0;
+ virtual int DeregisterInputObserver() = 0;
+
protected:
ViECapture() {}
virtual ~ViECapture() {}
};
} // namespace webrtc
#endif // WEBRTC_VIDEO_ENGINE_INCLUDE_VIE_CAPTURE_H_
--- a/media/webrtc/trunk/webrtc/video_engine/vie_capture_impl.cc
+++ b/media/webrtc/trunk/webrtc/video_engine/vie_capture_impl.cc
@@ -368,16 +368,24 @@ int ViECaptureImpl::RegisterObserver(con
}
if (vie_capture->RegisterObserver(&observer) != 0) {
shared_data_->SetLastError(kViECaptureDeviceUnknownError);
return -1;
}
return 0;
}
+int ViECaptureImpl::RegisterInputObserver(ViEInputObserver* observer) {
+ if (shared_data_->input_manager()->RegisterObserver(observer) != 0) {
+ shared_data_->SetLastError(kViECaptureDeviceUnknownError);
+ return -1;
+ }
+ return 0;
+}
+
int ViECaptureImpl::DeregisterObserver(const int capture_id) {
ViEInputManagerScoped is(*(shared_data_->input_manager()));
ViECapturer* vie_capture = is.Capture(capture_id);
if (!vie_capture) {
shared_data_->SetLastError(kViECaptureDeviceDoesNotExist);
return -1;
}
if (!vie_capture->IsObserverRegistered()) {
@@ -387,9 +395,17 @@ int ViECaptureImpl::DeregisterObserver(c
if (vie_capture->DeRegisterObserver() != 0) {
shared_data_->SetLastError(kViECaptureDeviceUnknownError);
return -1;
}
return 0;
}
+int ViECaptureImpl::DeregisterInputObserver() {
+ if (shared_data_->input_manager()->DeRegisterObserver() != 0) {
+ shared_data_->SetLastError(kViECaptureDeviceUnknownError);
+ return -1;
+ }
+ return 0;
+}
+
} // namespace webrtc
--- a/media/webrtc/trunk/webrtc/video_engine/vie_capture_impl.h
+++ b/media/webrtc/trunk/webrtc/video_engine/vie_capture_impl.h
@@ -61,17 +61,19 @@ class ViECaptureImpl
const char* unique_idUTF8, const unsigned int unique_idUTF8Length,
const char* dialog_title, void* parent_window = NULL,
const unsigned int x = 200, const unsigned int y = 200);
virtual int GetOrientation(const char* unique_idUTF8,
VideoRotation& orientation);
virtual int EnableBrightnessAlarm(const int capture_id, const bool enable);
virtual int RegisterObserver(const int capture_id,
ViECaptureObserver& observer);
+ virtual int RegisterInputObserver(ViEInputObserver* observer);
virtual int DeregisterObserver(const int capture_id);
+ virtual int DeregisterInputObserver();
protected:
explicit ViECaptureImpl(ViESharedData* shared_data);
virtual ~ViECaptureImpl();
private:
ViESharedData* shared_data_;
};
--- a/media/webrtc/trunk/webrtc/video_engine/vie_input_manager.cc
+++ b/media/webrtc/trunk/webrtc/video_engine/vie_input_manager.cc
@@ -27,16 +27,18 @@
namespace webrtc {
ViEInputManager::ViEInputManager(const int engine_id, const Config& config)
: config_(config),
engine_id_(engine_id),
map_cs_(CriticalSectionWrapper::CreateCriticalSection()),
device_info_cs_(CriticalSectionWrapper::CreateCriticalSection()),
+ observer_cs_(CriticalSectionWrapper::CreateCriticalSection()),
+ observer_(NULL),
vie_frame_provider_map_(),
capture_device_info_(NULL),
module_process_thread_(NULL) {
for (int idx = 0; idx < kViEMaxCaptureDevices; idx++) {
free_capture_device_id_[idx] = true;
}
}
@@ -329,16 +331,23 @@ ViECapturer* ViEInputManager::ViECapture
capture_id <= kViECaptureIdBase + kViEMaxCaptureDevices)) {
LOG(LS_ERROR) << "Capture device doesn't exist " << capture_id << ".";
return NULL;
}
return static_cast<ViECapturer*>(ViEFrameProvider(capture_id));
}
+void ViEInputManager::OnDeviceChange() {
+ CriticalSectionScoped cs(observer_cs_.get());
+ if (observer_) {
+ observer_->DeviceChange();
+ }
+}
+
// Create different DeviceInfo by _config;
VideoCaptureModule::DeviceInfo* ViEInputManager::GetDeviceInfo() {
CaptureDeviceType type = config_.Get<CaptureDeviceInfo>().type;
if (capture_device_info_ == NULL) {
switch (type) {
case CaptureDeviceType::Screen:
case CaptureDeviceType::Application:
@@ -356,16 +365,45 @@ VideoCaptureModule::DeviceInfo* ViEInput
break;
default:
// Don't try to build anything for unknown/unsupported types
break;
}
}
return capture_device_info_;
}
+
+int32_t ViEInputManager::RegisterObserver(ViEInputObserver* observer) {
+ {
+ CriticalSectionScoped cs(observer_cs_.get());
+ if (observer_) {
+ LOG_F(LS_ERROR) << "Observer already registered.";
+ return -1;
+ }
+ observer_ = observer;
+ }
+
+ if (!GetDeviceInfo())
+ return -1;
+
+ if (capture_device_info_ != NULL)
+ capture_device_info_->RegisterVideoInputFeedBack(*this);
+
+ return 0;
+}
+
+int32_t ViEInputManager::DeRegisterObserver() {
+ if (capture_device_info_ != NULL)
+ capture_device_info_->DeRegisterVideoInputFeedBack();
+
+ CriticalSectionScoped cs(observer_cs_.get());
+ observer_ = NULL;
+ return 0;
+}
+
ViEInputManagerScoped::ViEInputManagerScoped(
const ViEInputManager& vie_input_manager)
: ViEManagerScopedBase(vie_input_manager) {
}
ViECapturer* ViEInputManagerScoped::Capture(int capture_id) const {
return static_cast<const ViEInputManager*>(vie_manager_)->ViECapturePtr(
capture_id);
--- a/media/webrtc/trunk/webrtc/video_engine/vie_input_manager.h
+++ b/media/webrtc/trunk/webrtc/video_engine/vie_input_manager.h
@@ -27,17 +27,18 @@ namespace webrtc {
class Config;
class CriticalSectionWrapper;
class ProcessThread;
class RWLockWrapper;
class ViECapturer;
class ViEExternalCapture;
class VoiceEngine;
-class ViEInputManager : private ViEManagerBase {
+class ViEInputManager : private ViEManagerBase,
+ protected VideoInputFeedBack {
friend class ViEInputManagerScoped;
public:
ViEInputManager(int engine_id, const Config& config);
~ViEInputManager();
void SetModuleProcessThread(ProcessThread* module_process_thread);
// Returns number of capture devices.
@@ -73,18 +74,22 @@ class ViEInputManager : private ViEManag
int CreateCaptureDevice(const char* device_unique_idUTF8,
const uint32_t device_unique_idUTF8Length,
int& capture_id);
int CreateCaptureDevice(VideoCaptureModule* capture_module,
int& capture_id);
int CreateExternalCaptureDevice(ViEExternalCapture*& external_capture,
int& capture_id);
int DestroyCaptureDevice(int capture_id);
+ int32_t RegisterObserver(ViEInputObserver* observer);
+ int32_t DeRegisterObserver();
protected:
VideoCaptureModule::DeviceInfo* GetDeviceInfo();
+ // Implements VideoInputFeedBack.
+ virtual void OnDeviceChange();
private:
// Gets and allocates a free capture device id. Assumed protected by caller.
bool GetFreeCaptureId(int* freecapture_id);
// Frees a capture id assigned in GetFreeCaptureId.
void ReturnCaptureId(int capture_id);
// Gets the ViEFrameProvider for this capture observer.
@@ -96,16 +101,18 @@ class ViEInputManager : private ViEManag
// Gets the ViECapturer for the capture device id.
ViECapturer* ViECapturePtr(int capture_id) const;
const Config& config_;
int engine_id_;
rtc::scoped_ptr<CriticalSectionWrapper> map_cs_;
rtc::scoped_ptr<CriticalSectionWrapper> device_info_cs_;
+ rtc::scoped_ptr<CriticalSectionWrapper> observer_cs_;
+ ViEInputObserver* observer_ GUARDED_BY(observer_cs_.get());
typedef std::map<int, ViEFrameProviderBase*> FrameProviderMap;
FrameProviderMap vie_frame_provider_map_;
// Capture devices.
VideoCaptureModule::DeviceInfo* capture_device_info_;
int free_capture_device_id_[kViEMaxCaptureDevices];
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4789,16 +4789,19 @@ pref("dom.w3c_touch_events.enabled", 2);
#endif
// W3C draft pointer events
pref("dom.w3c_pointer_events.enabled", false);
// W3C draft ImageCapture API
pref("dom.imagecapture.enabled", false);
+// W3C MediaDevices devicechange event
+pref("media.ondevicechange.enabled", false);
+
// W3C touch-action css property (related to touch and pointer events)
// Note that we turn this on even on platforms/configurations where touch
// events are not supported (e.g. OS X, or Windows with e10s disabled). For
// those platforms we don't handle touch events anyway so it's conceptually
// a no-op.
#ifdef NIGHTLY_BUILD
pref("layout.css.touch_action.enabled", true);
#else