1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "MediaManager.h"
9 #include "AudioCaptureTrack.h"
10 #include "AudioDeviceInfo.h"
11 #include "AudioStreamTrack.h"
12 #include "CubebDeviceEnumerator.h"
13 #include "CubebInputStream.h"
14 #include "MediaTimer.h"
15 #include "MediaTrackConstraints.h"
16 #include "MediaTrackGraph.h"
17 #include "MediaTrackListener.h"
18 #include "VideoStreamTrack.h"
20 #include "VideoUtils.h"
21 #include "mozilla/Base64.h"
22 #include "mozilla/EventTargetCapability.h"
23 #include "mozilla/MozPromise.h"
24 #include "mozilla/NullPrincipal.h"
25 #include "mozilla/PeerIdentity.h"
26 #include "mozilla/PermissionDelegateHandler.h"
27 #include "mozilla/Sprintf.h"
28 #include "mozilla/StaticPrefs_media.h"
29 #include "mozilla/Telemetry.h"
30 #include "mozilla/Types.h"
31 #include "mozilla/dom/BindingDeclarations.h"
32 #include "mozilla/dom/Document.h"
33 #include "mozilla/dom/Element.h"
34 #include "mozilla/dom/FeaturePolicyUtils.h"
35 #include "mozilla/dom/File.h"
36 #include "mozilla/dom/GetUserMediaRequestBinding.h"
37 #include "mozilla/dom/MediaDeviceInfo.h"
38 #include "mozilla/dom/MediaDevices.h"
39 #include "mozilla/dom/MediaDevicesBinding.h"
40 #include "mozilla/dom/MediaStreamBinding.h"
41 #include "mozilla/dom/MediaStreamTrackBinding.h"
42 #include "mozilla/dom/Promise.h"
43 #include "mozilla/dom/UserActivation.h"
44 #include "mozilla/dom/WindowContext.h"
45 #include "mozilla/dom/WindowGlobalChild.h"
46 #include "mozilla/ipc/BackgroundChild.h"
47 #include "mozilla/ipc/PBackgroundChild.h"
48 #include "mozilla/media/CamerasTypes.h"
49 #include "mozilla/media/MediaChild.h"
50 #include "mozilla/media/MediaTaskUtils.h"
51 #include "nsAppDirectoryServiceDefs.h"
53 #include "nsContentUtils.h"
54 #include "nsGlobalWindowInner.h"
55 #include "nsHashPropertyBag.h"
56 #include "nsIEventTarget.h"
57 #include "nsIPermissionManager.h"
58 #include "nsIUUIDGenerator.h"
59 #include "nsJSUtils.h"
61 #include "nsNetUtil.h"
62 #include "nsProxyRelease.h"
67 /* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */
68 #include "MediaEngineFake.h"
69 #include "MediaEngineSource.h"
70 #if defined(MOZ_WEBRTC)
71 # include "MediaEngineWebRTC.h"
72 # include "MediaEngineWebRTCAudio.h"
73 # include "browser_logging/WebRtcLog.h"
74 # include "modules/audio_processing/include/audio_processing.h"
81 // A specialization of nsMainThreadPtrHolder for
82 // mozilla::dom::CallbackObjectHolder. See documentation for
83 // nsMainThreadPtrHolder in nsProxyRelease.h. This specialization lets us avoid
84 // wrapping the CallbackObjectHolder into a separate refcounted object.
85 template <class WebIDLCallbackT
, class XPCOMCallbackT
>
86 class nsMainThreadPtrHolder
<
87 mozilla::dom::CallbackObjectHolder
<WebIDLCallbackT
, XPCOMCallbackT
>>
89 typedef mozilla::dom::CallbackObjectHolder
<WebIDLCallbackT
, XPCOMCallbackT
>
93 nsMainThreadPtrHolder(const char* aName
, Holder
&& aHolder
)
94 : mHolder(std::move(aHolder
))
95 #ifndef RELEASE_OR_BETA
100 MOZ_ASSERT(NS_IsMainThread());
104 // We can be released on any thread.
105 ~nsMainThreadPtrHolder() {
106 if (NS_IsMainThread()) {
108 } else if (mHolder
.GetISupports()) {
109 nsCOMPtr
<nsIEventTarget
> target
= do_GetMainThread();
112 #ifdef RELEASE_OR_BETA
117 target
, mHolder
.Forget());
123 // Nobody should be touching the raw pointer off-main-thread.
124 if (MOZ_UNLIKELY(!NS_IsMainThread())) {
125 NS_ERROR("Can't dereference nsMainThreadPtrHolder off main thread");
131 bool operator!() const { return !mHolder
; }
133 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsMainThreadPtrHolder
<Holder
>)
139 #ifndef RELEASE_OR_BETA
140 const char* mName
= nullptr;
143 // Copy constructor and operator= not implemented. Once constructed, the
144 // holder is immutable.
145 Holder
& operator=(const nsMainThreadPtrHolder
& aOther
) = delete;
146 nsMainThreadPtrHolder(const nsMainThreadPtrHolder
& aOther
) = delete;
151 LazyLogModule
gMediaManagerLog("MediaManager");
152 #define LOG(...) MOZ_LOG(gMediaManagerLog, LogLevel::Debug, (__VA_ARGS__))
154 class GetUserMediaStreamTask
;
155 class LocalTrackSource
;
156 class SelectAudioOutputTask
;
158 using camera::CamerasAccessStatus
;
159 using dom::BFCacheStatus
;
160 using dom::CallerType
;
161 using dom::ConstrainDOMStringParameters
;
162 using dom::ConstrainDoubleRange
;
163 using dom::ConstrainLongRange
;
164 using dom::DisplayMediaStreamConstraints
;
167 using dom::FeaturePolicyUtils
;
169 using dom::GetUserMediaRequest
;
170 using dom::MediaDeviceKind
;
171 using dom::MediaDevices
;
172 using dom::MediaSourceEnum
;
173 using dom::MediaStreamConstraints
;
174 using dom::MediaStreamError
;
175 using dom::MediaStreamTrack
;
176 using dom::MediaStreamTrackSource
;
177 using dom::MediaTrackCapabilities
;
178 using dom::MediaTrackConstraints
;
179 using dom::MediaTrackConstraintSet
;
180 using dom::MediaTrackSettings
;
181 using dom::OwningBooleanOrMediaTrackConstraints
;
182 using dom::OwningStringOrStringSequence
;
183 using dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters
;
186 using dom::UserActivation
;
187 using dom::WindowGlobalChild
;
188 using ConstDeviceSetPromise
= MediaManager::ConstDeviceSetPromise
;
189 using DeviceSetPromise
= MediaManager::DeviceSetPromise
;
190 using LocalDevicePromise
= MediaManager::LocalDevicePromise
;
191 using LocalDeviceSetPromise
= MediaManager::LocalDeviceSetPromise
;
192 using LocalMediaDeviceSetRefCnt
= MediaManager::LocalMediaDeviceSetRefCnt
;
193 using MediaDeviceSetRefCnt
= MediaManager::MediaDeviceSetRefCnt
;
194 using media::NewRunnableFrom
;
195 using media::NewTaskFrom
;
196 using media::Refcountable
;
198 // Whether main thread actions of MediaManager shutdown (except for clearing
199 // of sSingleton) have completed.
200 static bool sHasMainThreadShutdown
;
203 DeviceState(RefPtr
<LocalMediaDevice
> aDevice
,
204 RefPtr
<LocalTrackSource
> aTrackSource
, bool aOffWhileDisabled
)
205 : mOffWhileDisabled(aOffWhileDisabled
),
206 mDevice(std::move(aDevice
)),
207 mTrackSource(std::move(aTrackSource
)) {
209 MOZ_ASSERT(mTrackSource
);
212 // true if we have stopped mDevice, this is a terminal state.
214 bool mStopped
= false;
216 // true if mDevice is currently enabled.
217 // A device must be both enabled and unmuted to be turned on and capturing.
219 bool mDeviceEnabled
= false;
221 // true if mDevice is currently muted.
222 // A device that is either muted or disabled is turned off and not capturing.
226 // true if the application has currently enabled mDevice.
228 bool mTrackEnabled
= false;
230 // Time when the application last enabled mDevice.
232 TimeStamp mTrackEnabledTime
;
234 // true if an operation to Start() or Stop() mDevice has been dispatched to
235 // the media thread and is not finished yet.
237 bool mOperationInProgress
= false;
239 // true if we are allowed to turn off the underlying source while all tracks
240 // are disabled. Only affects disabling; always turns off on user-agent mute.
242 bool mOffWhileDisabled
= false;
244 // Timer triggered by a MediaStreamTrackSource signaling that all tracks got
245 // disabled. When the timer fires we initiate Stop()ing mDevice.
246 // If set we allow dynamically stopping and starting mDevice.
248 const RefPtr
<MediaTimer
<TimeStamp
>> mDisableTimer
=
249 new MediaTimer
<TimeStamp
>();
251 // The underlying device we keep state for. Always non-null.
252 // Threadsafe access, but see method declarations for individual constraints.
253 const RefPtr
<LocalMediaDevice
> mDevice
;
255 // The MediaStreamTrackSource for any tracks (original and clones) originating
256 // from this device. Always non-null. Threadsafe access, but see method
257 // declarations for individual constraints.
258 const RefPtr
<LocalTrackSource
> mTrackSource
;
262 * This mimics the capture state from nsIMediaManagerService.
264 enum class CaptureState
: uint16_t {
265 Off
= nsIMediaManagerService::STATE_NOCAPTURE
,
266 Enabled
= nsIMediaManagerService::STATE_CAPTURE_ENABLED
,
267 Disabled
= nsIMediaManagerService::STATE_CAPTURE_DISABLED
,
270 static CaptureState
CombineCaptureState(CaptureState aFirst
,
271 CaptureState aSecond
) {
272 if (aFirst
== CaptureState::Enabled
|| aSecond
== CaptureState::Enabled
) {
273 return CaptureState::Enabled
;
275 if (aFirst
== CaptureState::Disabled
|| aSecond
== CaptureState::Disabled
) {
276 return CaptureState::Disabled
;
278 MOZ_ASSERT(aFirst
== CaptureState::Off
);
279 MOZ_ASSERT(aSecond
== CaptureState::Off
);
280 return CaptureState::Off
;
283 static uint16_t FromCaptureState(CaptureState aState
) {
284 MOZ_ASSERT(aState
== CaptureState::Off
|| aState
== CaptureState::Enabled
||
285 aState
== CaptureState::Disabled
);
286 return static_cast<uint16_t>(aState
);
289 void MediaManager::CallOnError(GetUserMediaErrorCallback
& aCallback
,
290 MediaStreamError
& aError
) {
291 aCallback
.Call(aError
);
294 void MediaManager::CallOnSuccess(GetUserMediaSuccessCallback
& aCallback
,
295 DOMMediaStream
& aStream
) {
296 aCallback
.Call(aStream
);
299 enum class PersistentPermissionState
: uint32_t {
300 Unknown
= nsIPermissionManager::UNKNOWN_ACTION
,
301 Allow
= nsIPermissionManager::ALLOW_ACTION
,
302 Deny
= nsIPermissionManager::DENY_ACTION
,
303 Prompt
= nsIPermissionManager::PROMPT_ACTION
,
306 static PersistentPermissionState
CheckPermission(
307 PersistentPermissionState aPermission
) {
308 switch (aPermission
) {
309 case PersistentPermissionState::Unknown
:
310 case PersistentPermissionState::Allow
:
311 case PersistentPermissionState::Deny
:
312 case PersistentPermissionState::Prompt
:
315 MOZ_CRASH("Unexpected permission value");
318 struct WindowPersistentPermissionState
{
319 PersistentPermissionState mCameraPermission
;
320 PersistentPermissionState mMicrophonePermission
;
323 static Result
<WindowPersistentPermissionState
, nsresult
>
324 GetPersistentPermissions(uint64_t aWindowId
) {
325 auto* window
= nsGlobalWindowInner::GetInnerWindowWithId(aWindowId
);
326 if (NS_WARN_IF(!window
) || NS_WARN_IF(!window
->GetPrincipal())) {
327 return Err(NS_ERROR_INVALID_ARG
);
330 Document
* doc
= window
->GetExtantDoc();
331 if (NS_WARN_IF(!doc
)) {
332 return Err(NS_ERROR_INVALID_ARG
);
335 nsIPrincipal
* principal
= window
->GetPrincipal();
336 if (NS_WARN_IF(!principal
)) {
337 return Err(NS_ERROR_INVALID_ARG
);
341 RefPtr
<PermissionDelegateHandler
> permDelegate
=
342 doc
->GetPermissionDelegateHandler();
343 if (NS_WARN_IF(!permDelegate
)) {
344 return Err(NS_ERROR_INVALID_ARG
);
347 uint32_t audio
= nsIPermissionManager::UNKNOWN_ACTION
;
348 uint32_t video
= nsIPermissionManager::UNKNOWN_ACTION
;
350 rv
= permDelegate
->GetPermission("microphone"_ns
, &audio
, true);
351 if (NS_WARN_IF(NS_FAILED(rv
))) {
354 rv
= permDelegate
->GetPermission("camera"_ns
, &video
, true);
355 if (NS_WARN_IF(NS_FAILED(rv
))) {
360 return WindowPersistentPermissionState
{
361 CheckPermission(static_cast<PersistentPermissionState
>(video
)),
362 CheckPermission(static_cast<PersistentPermissionState
>(audio
))};
366 * DeviceListener has threadsafe refcounting for use across the main, media and
367 * MTG threads. But it has a non-threadsafe SupportsWeakPtr for WeakPtr usage
368 * only from main thread, to ensure that garbage- and cycle-collected objects
369 * don't hold a reference to it during late shutdown.
371 class DeviceListener
: public SupportsWeakPtr
{
373 typedef MozPromise
<bool /* aIgnored */, RefPtr
<MediaMgrError
>, true>
374 DeviceListenerPromise
;
376 NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(
382 * Registers this device listener as belonging to the given window listener.
383 * Stop() must be called on registered DeviceListeners before destruction.
385 void Register(GetUserMediaWindowListener
* aListener
);
388 * Marks this listener as active and creates the internal device state.
390 void Activate(RefPtr
<LocalMediaDevice
> aDevice
,
391 RefPtr
<LocalTrackSource
> aTrackSource
, bool aStartMuted
);
394 * Posts a task to initialize and start the associated device.
396 RefPtr
<DeviceListenerPromise
> InitializeAsync();
399 * Posts a task to stop the device associated with this DeviceListener and
400 * notifies the associated window listener that a track was stopped.
402 * This will also clean up the weak reference to the associated window
403 * listener, and tell the window listener to remove its hard reference to this
404 * DeviceListener, so any caller will need to keep its own hard ref.
409 * Gets the main thread MediaTrackSettings from the MediaEngineSource
410 * associated with aTrack.
412 void GetSettings(MediaTrackSettings
& aOutSettings
) const;
415 * Gets the main thread MediaTrackCapabilities from the MediaEngineSource
416 * associated with aTrack.
418 void GetCapabilities(MediaTrackCapabilities
& aOutCapabilities
) const;
421 * Posts a task to set the enabled state of the device associated with this
422 * DeviceListener to aEnabled and notifies the associated window listener that
423 * a track's state has changed.
425 * Turning the hardware off while the device is disabled is supported for:
426 * - Camera (enabled by default, controlled by pref
427 * "media.getusermedia.camera.off_while_disabled.enabled")
428 * - Microphone (disabled by default, controlled by pref
429 * "media.getusermedia.microphone.off_while_disabled.enabled")
430 * Screen-, app-, or windowsharing is not supported at this time.
432 * The behavior is also different between disabling and enabling a device.
433 * While enabling is immediate, disabling only happens after a delay.
434 * This is now defaulting to 3 seconds but can be overriden by prefs:
435 * - "media.getusermedia.camera.off_while_disabled.delay_ms" and
436 * - "media.getusermedia.microphone.off_while_disabled.delay_ms".
438 * The delay is in place to prevent misuse by malicious sites. If a track is
439 * re-enabled before the delay has passed, the device will not be touched
440 * until another disable followed by the full delay happens.
442 void SetDeviceEnabled(bool aEnabled
);
445 * Posts a task to set the muted state of the device associated with this
446 * DeviceListener to aMuted and notifies the associated window listener that a
447 * track's state has changed.
449 * Turning the hardware off while the device is muted is supported for:
450 * - Camera (enabled by default, controlled by pref
451 * "media.getusermedia.camera.off_while_disabled.enabled")
452 * - Microphone (disabled by default, controlled by pref
453 * "media.getusermedia.microphone.off_while_disabled.enabled")
454 * Screen-, app-, or windowsharing is not supported at this time.
456 void SetDeviceMuted(bool aMuted
);
459 * Mutes or unmutes the associated video device if it is a camera.
461 void MuteOrUnmuteCamera(bool aMute
);
462 void MuteOrUnmuteMicrophone(bool aMute
);
464 LocalMediaDevice
* GetDevice() const {
465 return mDeviceState
? mDeviceState
->mDevice
.get() : nullptr;
468 bool Activated() const { return static_cast<bool>(mDeviceState
); }
470 bool Stopped() const { return mStopped
; }
472 bool CapturingVideo() const;
474 bool CapturingAudio() const;
476 CaptureState
CapturingSource(MediaSourceEnum aSource
) const;
478 RefPtr
<DeviceListenerPromise
> ApplyConstraints(
479 const MediaTrackConstraints
& aConstraints
, CallerType aCallerType
);
481 PrincipalHandle
GetPrincipalHandle() const;
483 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
484 size_t amount
= aMallocSizeOf(this);
485 // Assume mPrincipalHandle refers to a principal owned elsewhere.
486 // DeviceState does not have support for memory accounting.
491 virtual ~DeviceListener() {
492 MOZ_ASSERT(mStopped
);
493 MOZ_ASSERT(!mWindowListener
);
496 using DeviceOperationPromise
=
497 MozPromise
<nsresult
, bool, /* IsExclusive = */ true>;
500 * Posts a task to start or stop the device associated with aTrack, based on
501 * a passed-in boolean. Private method used by SetDeviceEnabled and
504 RefPtr
<DeviceOperationPromise
> UpdateDevice(bool aOn
);
506 // true after this listener has had all devices stopped. MainThread only.
509 // never ever indirect off this; just for assertions
510 PRThread
* mMainThreadCheck
;
512 // Set in Register() on main thread, then read from any thread.
513 PrincipalHandle mPrincipalHandle
;
515 // Weak pointer to the window listener that owns us. MainThread only.
516 GetUserMediaWindowListener
* mWindowListener
;
518 // Accessed from MediaTrackGraph thread, MediaManager thread, and MainThread
519 // No locking needed as it's set on Activate() and never assigned to again.
520 UniquePtr
<DeviceState
> mDeviceState
;
522 MediaEventListener mCaptureEndedListener
;
526 * This class represents a WindowID and handles all MediaTrackListeners
527 * (here subclassed as DeviceListeners) used to feed GetUserMedia tracks.
528 * It proxies feedback from them into messages for browser chrome.
529 * The DeviceListeners are used to Start() and Stop() the underlying
530 * MediaEngineSource when MediaStreams are assigned and deassigned in content.
532 class GetUserMediaWindowListener
{
536 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GetUserMediaWindowListener
)
538 // Create in an inactive state
539 GetUserMediaWindowListener(uint64_t aWindowID
,
540 const PrincipalHandle
& aPrincipalHandle
)
541 : mWindowID(aWindowID
),
542 mPrincipalHandle(aPrincipalHandle
),
543 mChromeNotificationTaskPosted(false) {}
546 * Registers an inactive gUM device listener for this WindowListener.
548 void Register(RefPtr
<DeviceListener
> aListener
) {
549 MOZ_ASSERT(NS_IsMainThread());
550 MOZ_ASSERT(aListener
);
551 MOZ_ASSERT(!aListener
->Activated());
552 MOZ_ASSERT(!mInactiveListeners
.Contains(aListener
), "Already registered");
553 MOZ_ASSERT(!mActiveListeners
.Contains(aListener
), "Already activated");
555 aListener
->Register(this);
556 mInactiveListeners
.AppendElement(std::move(aListener
));
560 * Activates an already registered and inactive gUM device listener for this
563 void Activate(RefPtr
<DeviceListener
> aListener
,
564 RefPtr
<LocalMediaDevice
> aDevice
,
565 RefPtr
<LocalTrackSource
> aTrackSource
) {
566 MOZ_ASSERT(NS_IsMainThread());
567 MOZ_ASSERT(aListener
);
568 MOZ_ASSERT(!aListener
->Activated());
569 MOZ_ASSERT(mInactiveListeners
.Contains(aListener
),
570 "Must be registered to activate");
571 MOZ_ASSERT(!mActiveListeners
.Contains(aListener
), "Already activated");
574 if (aDevice
->Kind() == MediaDeviceKind::Videoinput
) {
575 muted
= mCamerasAreMuted
;
576 } else if (aDevice
->Kind() == MediaDeviceKind::Audioinput
) {
577 muted
= mMicrophonesAreMuted
;
579 MOZ_CRASH("Unexpected device kind");
582 mInactiveListeners
.RemoveElement(aListener
);
583 aListener
->Activate(std::move(aDevice
), std::move(aTrackSource
), muted
);
584 mActiveListeners
.AppendElement(std::move(aListener
));
588 * Removes all DeviceListeners from this window listener.
589 * Removes this window listener from the list of active windows, so callers
590 * need to make sure to hold a strong reference.
593 MOZ_ASSERT(NS_IsMainThread());
595 for (auto& l
: mInactiveListeners
.Clone()) {
598 for (auto& l
: mActiveListeners
.Clone()) {
601 MOZ_ASSERT(mInactiveListeners
.Length() == 0);
602 MOZ_ASSERT(mActiveListeners
.Length() == 0);
604 MediaManager
* mgr
= MediaManager::GetIfExists();
606 MOZ_ASSERT(false, "MediaManager should stay until everything is removed");
609 GetUserMediaWindowListener
* windowListener
=
610 mgr
->GetWindowListener(mWindowID
);
612 if (!windowListener
) {
613 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
614 auto* globalWindow
= nsGlobalWindowInner::GetInnerWindowWithId(mWindowID
);
616 auto req
= MakeRefPtr
<GetUserMediaRequest
>(
617 globalWindow
, VoidString(), VoidString(),
618 UserActivation::IsHandlingUserInput());
619 obs
->NotifyWhenScriptSafe(req
, "recording-device-stopped", nullptr);
624 MOZ_ASSERT(windowListener
== this,
625 "There should only be one window listener per window ID");
627 LOG("GUMWindowListener %p removing windowID %" PRIu64
, this, mWindowID
);
628 mgr
->RemoveWindowID(mWindowID
);
632 * Removes a listener from our lists. Safe to call without holding a hard
633 * reference. That said, you'll still want to iterate on a copy of said lists,
634 * if you end up calling this method (or methods that may call this method) in
635 * the loop, to avoid inadvertently skipping members.
637 * For use only from GetUserMediaWindowListener and DeviceListener.
639 bool Remove(RefPtr
<DeviceListener
> aListener
) {
640 // We refcount aListener on entry since we're going to proxy-release it
641 // below to prevent the refcount going to zero on callers who might be
642 // inside the listener, but operating without a hard reference to self.
643 MOZ_ASSERT(NS_IsMainThread());
645 if (!mInactiveListeners
.RemoveElement(aListener
) &&
646 !mActiveListeners
.RemoveElement(aListener
)) {
649 MOZ_ASSERT(!mInactiveListeners
.Contains(aListener
),
650 "A DeviceListener should only be once in one of "
651 "mInactiveListeners and mActiveListeners");
652 MOZ_ASSERT(!mActiveListeners
.Contains(aListener
),
653 "A DeviceListener should only be once in one of "
654 "mInactiveListeners and mActiveListeners");
656 LOG("GUMWindowListener %p stopping DeviceListener %p.", this,
660 if (LocalMediaDevice
* removedDevice
= aListener
->GetDevice()) {
661 bool revokePermission
= true;
662 nsString removedRawId
;
663 nsString removedSourceType
;
664 removedDevice
->GetRawId(removedRawId
);
665 removedDevice
->GetMediaSource(removedSourceType
);
667 for (const auto& l
: mActiveListeners
) {
668 if (LocalMediaDevice
* device
= l
->GetDevice()) {
670 device
->GetRawId(rawId
);
671 if (removedRawId
.Equals(rawId
)) {
672 revokePermission
= false;
678 if (revokePermission
) {
679 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
680 auto* window
= nsGlobalWindowInner::GetInnerWindowWithId(mWindowID
);
681 auto req
= MakeRefPtr
<GetUserMediaRequest
>(
682 window
, removedRawId
, removedSourceType
,
683 UserActivation::IsHandlingUserInput());
684 obs
->NotifyWhenScriptSafe(req
, "recording-device-stopped", nullptr);
688 if (mInactiveListeners
.Length() == 0 && mActiveListeners
.Length() == 0) {
689 LOG("GUMWindowListener %p Removed last DeviceListener. Cleaning up.",
694 nsCOMPtr
<nsIEventTarget
> mainTarget
= do_GetMainThread();
695 // To allow being invoked by callers not holding a strong reference to self,
696 // hold the listener alive until the stack has unwound, by always
697 // dispatching a runnable (aAlwaysProxy = true)
698 NS_ProxyRelease(__func__
, mainTarget
, aListener
.forget(), true);
703 * Stops all screen/window/audioCapture sharing, but not camera or microphone.
707 void StopRawID(const nsString
& removedDeviceID
);
709 void MuteOrUnmuteCameras(bool aMute
);
710 void MuteOrUnmuteMicrophones(bool aMute
);
713 * Called by one of our DeviceListeners when one of its tracks has changed so
714 * that chrome state is affected.
715 * Schedules an event for the next stable state to update chrome.
717 void ChromeAffectingStateChanged();
720 * Called in stable state to send a notification to update chrome.
724 bool CapturingVideo() const {
725 MOZ_ASSERT(NS_IsMainThread());
726 for (auto& l
: mActiveListeners
) {
727 if (l
->CapturingVideo()) {
734 bool CapturingAudio() const {
735 MOZ_ASSERT(NS_IsMainThread());
736 for (auto& l
: mActiveListeners
) {
737 if (l
->CapturingAudio()) {
744 CaptureState
CapturingSource(MediaSourceEnum aSource
) const {
745 MOZ_ASSERT(NS_IsMainThread());
746 CaptureState result
= CaptureState::Off
;
747 for (auto& l
: mActiveListeners
) {
748 result
= CombineCaptureState(result
, l
->CapturingSource(aSource
));
753 RefPtr
<LocalMediaDeviceSetRefCnt
> GetDevices() {
754 RefPtr devices
= new LocalMediaDeviceSetRefCnt();
755 for (auto& l
: mActiveListeners
) {
756 devices
->AppendElement(l
->GetDevice());
761 uint64_t WindowID() const { return mWindowID
; }
763 PrincipalHandle
GetPrincipalHandle() const { return mPrincipalHandle
; }
765 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
766 size_t amount
= aMallocSizeOf(this);
767 // Assume mPrincipalHandle refers to a principal owned elsewhere.
768 amount
+= mInactiveListeners
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
769 for (const RefPtr
<DeviceListener
>& listener
: mInactiveListeners
) {
770 amount
+= listener
->SizeOfIncludingThis(aMallocSizeOf
);
772 amount
+= mActiveListeners
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
773 for (const RefPtr
<DeviceListener
>& listener
: mActiveListeners
) {
774 amount
+= listener
->SizeOfIncludingThis(aMallocSizeOf
);
780 ~GetUserMediaWindowListener() {
781 MOZ_ASSERT(mInactiveListeners
.Length() == 0,
782 "Inactive listeners should already be removed");
783 MOZ_ASSERT(mActiveListeners
.Length() == 0,
784 "Active listeners should already be removed");
788 const PrincipalHandle mPrincipalHandle
;
790 // true if we have scheduled a task to notify chrome in the next stable state.
791 // The task will reset this to false. MainThread only.
792 bool mChromeNotificationTaskPosted
;
794 nsTArray
<RefPtr
<DeviceListener
>> mInactiveListeners
;
795 nsTArray
<RefPtr
<DeviceListener
>> mActiveListeners
;
797 // Whether camera and microphone access in this window are currently
798 // User Agent (UA) muted. When true, new and cloned tracks must start
799 // out muted, to avoid JS circumventing UA mute. Per-camera and
800 // per-microphone UA muting is not supported.
801 bool mCamerasAreMuted
= false;
802 bool mMicrophonesAreMuted
= false;
805 class LocalTrackSource
: public MediaStreamTrackSource
{
807 LocalTrackSource(nsIPrincipal
* aPrincipal
, const nsString
& aLabel
,
808 const RefPtr
<DeviceListener
>& aListener
,
809 MediaSourceEnum aSource
, MediaTrack
* aTrack
,
810 RefPtr
<PeerIdentity
> aPeerIdentity
,
811 TrackingId aTrackingId
= TrackingId())
812 : MediaStreamTrackSource(aPrincipal
, aLabel
, std::move(aTrackingId
)),
815 mPeerIdentity(std::move(aPeerIdentity
)),
816 mListener(aListener
.get()) {}
818 MediaSourceEnum
GetMediaSource() const override
{ return mSource
; }
820 const PeerIdentity
* GetPeerIdentity() const override
{ return mPeerIdentity
; }
822 RefPtr
<MediaStreamTrackSource::ApplyConstraintsPromise
> ApplyConstraints(
823 const MediaTrackConstraints
& aConstraints
,
824 CallerType aCallerType
) override
{
825 MOZ_ASSERT(NS_IsMainThread());
826 if (sHasMainThreadShutdown
|| !mListener
) {
827 // Track has been stopped, or we are in shutdown. In either case
828 // there's no observable outcome, so pretend we succeeded.
829 return MediaStreamTrackSource::ApplyConstraintsPromise::CreateAndResolve(
832 return mListener
->ApplyConstraints(aConstraints
, aCallerType
);
835 void GetSettings(MediaTrackSettings
& aOutSettings
) override
{
837 mListener
->GetSettings(aOutSettings
);
841 void GetCapabilities(MediaTrackCapabilities
& aOutCapabilities
) override
{
843 mListener
->GetCapabilities(aOutCapabilities
);
847 void Stop() override
{
852 if (!mTrack
->IsDestroyed()) {
857 void Disable() override
{
859 mListener
->SetDeviceEnabled(false);
863 void Enable() override
{
865 mListener
->SetDeviceEnabled(true);
871 mTrack
->SetDisabledTrackMode(DisabledTrackMode::SILENCE_BLACK
);
876 mTrack
->SetDisabledTrackMode(DisabledTrackMode::ENABLED
);
879 const MediaSourceEnum mSource
;
880 const RefPtr
<MediaTrack
> mTrack
;
881 const RefPtr
<const PeerIdentity
> mPeerIdentity
;
884 ~LocalTrackSource() {
885 MOZ_ASSERT(NS_IsMainThread());
886 MOZ_ASSERT(mTrack
->IsDestroyed());
889 // This is a weak pointer to avoid having the DeviceListener (which may
890 // have references to threads and threadpools) kept alive by DOM-objects
891 // that may have ref-cycles and thus are released very late during
892 // shutdown, even after xpcom-shutdown-threads. See bug 1351655 for what
894 WeakPtr
<DeviceListener
> mListener
;
897 class AudioCaptureTrackSource
: public LocalTrackSource
{
899 AudioCaptureTrackSource(nsIPrincipal
* aPrincipal
, nsPIDOMWindowInner
* aWindow
,
900 const nsString
& aLabel
,
901 AudioCaptureTrack
* aAudioCaptureTrack
,
902 RefPtr
<PeerIdentity
> aPeerIdentity
)
903 : LocalTrackSource(aPrincipal
, aLabel
, nullptr,
904 MediaSourceEnum::AudioCapture
, aAudioCaptureTrack
,
905 std::move(aPeerIdentity
)),
907 mAudioCaptureTrack(aAudioCaptureTrack
) {
908 mAudioCaptureTrack
->Start();
909 mAudioCaptureTrack
->Graph()->RegisterCaptureTrackForWindow(
910 mWindow
->WindowID(), mAudioCaptureTrack
);
911 mWindow
->SetAudioCapture(true);
914 void Stop() override
{
915 MOZ_ASSERT(NS_IsMainThread());
916 if (!mAudioCaptureTrack
->IsDestroyed()) {
918 mWindow
->SetAudioCapture(false);
919 mAudioCaptureTrack
->Graph()->UnregisterCaptureTrackForWindow(
920 mWindow
->WindowID());
923 // LocalTrackSource destroys the track.
924 LocalTrackSource::Stop();
925 MOZ_ASSERT(mAudioCaptureTrack
->IsDestroyed());
928 ProcessedMediaTrack
* InputTrack() const { return mAudioCaptureTrack
.get(); }
931 ~AudioCaptureTrackSource() {
932 MOZ_ASSERT(NS_IsMainThread());
933 MOZ_ASSERT(mAudioCaptureTrack
->IsDestroyed());
936 RefPtr
<nsPIDOMWindowInner
> mWindow
;
937 const RefPtr
<AudioCaptureTrack
> mAudioCaptureTrack
;
941 * nsIMediaDevice implementation.
943 NS_IMPL_ISUPPORTS(LocalMediaDevice
, nsIMediaDevice
)
945 MediaDevice::MediaDevice(MediaEngine
* aEngine
, MediaSourceEnum aMediaSource
,
946 const nsString
& aRawName
, const nsString
& aRawID
,
947 const nsString
& aRawGroupID
, IsScary aIsScary
,
948 const OsPromptable canRequestOsLevelPrompt
,
949 const IsPlaceholder aIsPlaceholder
)
951 mAudioDeviceInfo(nullptr),
952 mMediaSource(aMediaSource
),
953 mKind(MediaEngineSource::IsVideo(aMediaSource
)
954 ? MediaDeviceKind::Videoinput
955 : MediaDeviceKind::Audioinput
),
956 mScary(aIsScary
== IsScary::Yes
),
957 mCanRequestOsLevelPrompt(canRequestOsLevelPrompt
== OsPromptable::Yes
),
958 mIsFake(mEngine
->IsFake()),
959 mIsPlaceholder(aIsPlaceholder
== IsPlaceholder::Yes
),
960 mType(NS_ConvertASCIItoUTF16(dom::GetEnumString(mKind
))),
962 mRawGroupID(aRawGroupID
),
967 MediaDevice::MediaDevice(MediaEngine
* aEngine
,
968 const RefPtr
<AudioDeviceInfo
>& aAudioDeviceInfo
,
969 const nsString
& aRawID
)
971 mAudioDeviceInfo(aAudioDeviceInfo
),
972 mMediaSource(mAudioDeviceInfo
->Type() == AudioDeviceInfo::TYPE_INPUT
973 ? MediaSourceEnum::Microphone
974 : MediaSourceEnum::Other
),
975 mKind(mMediaSource
== MediaSourceEnum::Microphone
976 ? MediaDeviceKind::Audioinput
977 : MediaDeviceKind::Audiooutput
),
979 mCanRequestOsLevelPrompt(false),
981 mIsPlaceholder(false),
982 mType(NS_ConvertASCIItoUTF16(dom::GetEnumString(mKind
))),
984 mRawGroupID(mAudioDeviceInfo
->GroupID()),
985 mRawName(mAudioDeviceInfo
->Name()) {}
988 RefPtr
<MediaDevice
> MediaDevice::CopyWithNewRawGroupId(
989 const RefPtr
<MediaDevice
>& aOther
, const nsString
& aRawGroupID
) {
990 MOZ_ASSERT(!aOther
->mAudioDeviceInfo
, "device not supported");
991 return new MediaDevice(aOther
->mEngine
, aOther
->mMediaSource
,
992 aOther
->mRawName
, aOther
->mRawID
, aRawGroupID
,
993 IsScary(aOther
->mScary
),
994 OsPromptable(aOther
->mCanRequestOsLevelPrompt
),
995 IsPlaceholder(aOther
->mIsPlaceholder
));
998 MediaDevice::~MediaDevice() = default;
1000 LocalMediaDevice::LocalMediaDevice(RefPtr
<const MediaDevice
> aRawDevice
,
1001 const nsString
& aID
,
1002 const nsString
& aGroupID
,
1003 const nsString
& aName
)
1004 : mRawDevice(std::move(aRawDevice
)),
1007 mGroupID(aGroupID
) {
1008 MOZ_ASSERT(mRawDevice
);
1012 * Helper functions that implement the constraints algorithm from
1013 * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
1017 bool LocalMediaDevice::StringsContain(
1018 const OwningStringOrStringSequence
& aStrings
, nsString aN
) {
1019 return aStrings
.IsString() ? aStrings
.GetAsString() == aN
1020 : aStrings
.GetAsStringSequence().Contains(aN
);
1024 uint32_t LocalMediaDevice::FitnessDistance(
1025 nsString aN
, const ConstrainDOMStringParameters
& aParams
) {
1026 if (aParams
.mExact
.WasPassed() &&
1027 !StringsContain(aParams
.mExact
.Value(), aN
)) {
1030 if (aParams
.mIdeal
.WasPassed() &&
1031 !StringsContain(aParams
.mIdeal
.Value(), aN
)) {
1037 // Binding code doesn't templatize well...
1040 uint32_t LocalMediaDevice::FitnessDistance(
1042 const OwningStringOrStringSequenceOrConstrainDOMStringParameters
&
1044 if (aConstraint
.IsString()) {
1045 ConstrainDOMStringParameters params
;
1046 params
.mIdeal
.Construct();
1047 params
.mIdeal
.Value().SetAsString() = aConstraint
.GetAsString();
1048 return FitnessDistance(aN
, params
);
1049 } else if (aConstraint
.IsStringSequence()) {
1050 ConstrainDOMStringParameters params
;
1051 params
.mIdeal
.Construct();
1052 params
.mIdeal
.Value().SetAsStringSequence() =
1053 aConstraint
.GetAsStringSequence();
1054 return FitnessDistance(aN
, params
);
1056 return FitnessDistance(aN
, aConstraint
.GetAsConstrainDOMStringParameters());
1060 uint32_t LocalMediaDevice::GetBestFitnessDistance(
1061 const nsTArray
<const NormalizedConstraintSet
*>& aConstraintSets
,
1062 CallerType aCallerType
) {
1063 MOZ_ASSERT(MediaManager::IsInMediaThread());
1064 MOZ_ASSERT(GetMediaSource() != MediaSourceEnum::Other
);
1066 bool isChrome
= aCallerType
== CallerType::System
;
1067 const nsString
& id
= isChrome
? RawID() : mID
;
1068 auto type
= GetMediaSource();
1069 uint64_t distance
= 0;
1070 if (!aConstraintSets
.IsEmpty()) {
1071 if (isChrome
/* For the screen/window sharing preview */ ||
1072 type
== MediaSourceEnum::Camera
||
1073 type
== MediaSourceEnum::Microphone
) {
1074 distance
+= uint64_t(MediaConstraintsHelper::FitnessDistance(
1075 Some(id
), aConstraintSets
[0]->mDeviceId
)) +
1076 uint64_t(MediaConstraintsHelper::FitnessDistance(
1077 Some(mGroupID
), aConstraintSets
[0]->mGroupId
));
1080 if (distance
< UINT32_MAX
) {
1081 // Forward request to underlying object to interrogate per-mode
1083 distance
+= Source()->GetBestFitnessDistance(aConstraintSets
);
1085 return std::min
<uint64_t>(distance
, UINT32_MAX
);
1089 LocalMediaDevice::GetRawName(nsAString
& aName
) {
1090 MOZ_ASSERT(NS_IsMainThread());
1091 aName
.Assign(mRawDevice
->mRawName
);
1096 LocalMediaDevice::GetType(nsAString
& aType
) {
1097 MOZ_ASSERT(NS_IsMainThread());
1098 aType
.Assign(mRawDevice
->mType
);
1103 LocalMediaDevice::GetRawId(nsAString
& aID
) {
1104 MOZ_ASSERT(NS_IsMainThread());
1105 aID
.Assign(RawID());
1110 LocalMediaDevice::GetId(nsAString
& aID
) {
1111 MOZ_ASSERT(NS_IsMainThread());
1117 LocalMediaDevice::GetScary(bool* aScary
) {
1118 *aScary
= mRawDevice
->mScary
;
1123 LocalMediaDevice::GetCanRequestOsLevelPrompt(bool* aCanRequestOsLevelPrompt
) {
1124 *aCanRequestOsLevelPrompt
= mRawDevice
->mCanRequestOsLevelPrompt
;
1128 void LocalMediaDevice::GetSettings(MediaTrackSettings
& aOutSettings
) {
1129 MOZ_ASSERT(NS_IsMainThread());
1130 Source()->GetSettings(aOutSettings
);
1133 void LocalMediaDevice::GetCapabilities(
1134 MediaTrackCapabilities
& aOutCapabilities
) {
1135 MOZ_ASSERT(NS_IsMainThread());
1136 Source()->GetCapabilities(aOutCapabilities
);
1139 MediaEngineSource
* LocalMediaDevice::Source() {
1141 mSource
= mRawDevice
->mEngine
->CreateSource(mRawDevice
);
1146 const TrackingId
& LocalMediaDevice::GetTrackingId() const {
1147 return mSource
->GetTrackingId();
1150 // Threadsafe since mKind and mSource are const.
1152 LocalMediaDevice::GetMediaSource(nsAString
& aMediaSource
) {
1153 if (Kind() == MediaDeviceKind::Audiooutput
) {
1154 aMediaSource
.Truncate();
1156 aMediaSource
.AssignASCII(dom::GetEnumString(GetMediaSource()));
1161 nsresult
LocalMediaDevice::Allocate(const MediaTrackConstraints
& aConstraints
,
1162 const MediaEnginePrefs
& aPrefs
,
1164 const char** aOutBadConstraint
) {
1165 MOZ_ASSERT(MediaManager::IsInMediaThread());
1167 // Mock failure for automated tests.
1168 if (IsFake() && aConstraints
.mDeviceId
.WasPassed() &&
1169 aConstraints
.mDeviceId
.Value().IsString() &&
1170 aConstraints
.mDeviceId
.Value().GetAsString().EqualsASCII("bad device")) {
1171 return NS_ERROR_FAILURE
;
1174 return Source()->Allocate(aConstraints
, aPrefs
, aWindowID
, aOutBadConstraint
);
1177 void LocalMediaDevice::SetTrack(const RefPtr
<MediaTrack
>& aTrack
,
1178 const PrincipalHandle
& aPrincipalHandle
) {
1179 MOZ_ASSERT(MediaManager::IsInMediaThread());
1180 Source()->SetTrack(aTrack
, aPrincipalHandle
);
1183 nsresult
LocalMediaDevice::Start() {
1184 MOZ_ASSERT(MediaManager::IsInMediaThread());
1185 MOZ_ASSERT(Source());
1186 return Source()->Start();
1189 nsresult
LocalMediaDevice::Reconfigure(
1190 const MediaTrackConstraints
& aConstraints
, const MediaEnginePrefs
& aPrefs
,
1191 const char** aOutBadConstraint
) {
1192 MOZ_ASSERT(MediaManager::IsInMediaThread());
1193 auto type
= GetMediaSource();
1194 if (type
== MediaSourceEnum::Camera
|| type
== MediaSourceEnum::Microphone
) {
1195 NormalizedConstraints
c(aConstraints
);
1196 if (MediaConstraintsHelper::FitnessDistance(Some(mID
), c
.mDeviceId
) ==
1198 *aOutBadConstraint
= "deviceId";
1199 return NS_ERROR_INVALID_ARG
;
1201 if (MediaConstraintsHelper::FitnessDistance(Some(mGroupID
), c
.mGroupId
) ==
1203 *aOutBadConstraint
= "groupId";
1204 return NS_ERROR_INVALID_ARG
;
1207 return Source()->Reconfigure(aConstraints
, aPrefs
, aOutBadConstraint
);
1210 nsresult
LocalMediaDevice::FocusOnSelectedSource() {
1211 MOZ_ASSERT(MediaManager::IsInMediaThread());
1212 return Source()->FocusOnSelectedSource();
1215 nsresult
LocalMediaDevice::Stop() {
1216 MOZ_ASSERT(MediaManager::IsInMediaThread());
1217 MOZ_ASSERT(mSource
);
1218 return mSource
->Stop();
1221 nsresult
LocalMediaDevice::Deallocate() {
1222 MOZ_ASSERT(MediaManager::IsInMediaThread());
1223 MOZ_ASSERT(mSource
);
1224 return mSource
->Deallocate();
1227 MediaSourceEnum
MediaDevice::GetMediaSource() const { return mMediaSource
; }
1229 static const MediaTrackConstraints
& GetInvariant(
1230 const OwningBooleanOrMediaTrackConstraints
& aUnion
) {
1231 static const MediaTrackConstraints empty
;
1232 return aUnion
.IsMediaTrackConstraints() ? aUnion
.GetAsMediaTrackConstraints()
1236 // Source getter returning full list
1238 static void GetMediaDevices(MediaEngine
* aEngine
, MediaSourceEnum aSrcType
,
1239 MediaManager::MediaDeviceSet
& aResult
,
1240 const char* aMediaDeviceName
= nullptr) {
1241 MOZ_ASSERT(MediaManager::IsInMediaThread());
1243 LOG("%s: aEngine=%p, aSrcType=%" PRIu8
", aMediaDeviceName=%s", __func__
,
1244 aEngine
, static_cast<uint8_t>(aSrcType
),
1245 aMediaDeviceName
? aMediaDeviceName
: "null");
1246 nsTArray
<RefPtr
<MediaDevice
>> devices
;
1247 aEngine
->EnumerateDevices(aSrcType
, MediaSinkEnum::Other
, &devices
);
1250 * We're allowing multiple tabs to access the same camera for parity
1251 * with Chrome. See bug 811757 for some of the issues surrounding
1252 * this decision. To disallow, we'd filter by IsAvailable() as we used
1255 if (aMediaDeviceName
&& *aMediaDeviceName
) {
1256 for (auto& device
: devices
) {
1257 if (device
->mRawName
.EqualsASCII(aMediaDeviceName
)) {
1258 aResult
.AppendElement(device
);
1259 LOG("%s: found aMediaDeviceName=%s", __func__
, aMediaDeviceName
);
1264 aResult
= std::move(devices
);
1265 if (MOZ_LOG_TEST(gMediaManagerLog
, mozilla::LogLevel::Debug
)) {
1266 for (auto& device
: aResult
) {
1267 LOG("%s: appending device=%s", __func__
,
1268 NS_ConvertUTF16toUTF8(device
->mRawName
).get());
1274 RefPtr
<LocalDeviceSetPromise
> MediaManager::SelectSettings(
1275 const MediaStreamConstraints
& aConstraints
, CallerType aCallerType
,
1276 RefPtr
<LocalMediaDeviceSetRefCnt
> aDevices
) {
1277 MOZ_ASSERT(NS_IsMainThread());
1279 // Algorithm accesses device capabilities code and must run on media thread.
1280 // Modifies passed-in aDevices.
1282 return MediaManager::Dispatch
<LocalDeviceSetPromise
>(
1283 __func__
, [aConstraints
, devices
= std::move(aDevices
),
1284 aCallerType
](MozPromiseHolder
<LocalDeviceSetPromise
>& holder
) {
1285 auto& devicesRef
= *devices
;
1287 // Since the advanced part of the constraints algorithm needs to know
1288 // when a candidate set is overconstrained (zero members), we must split
1289 // up the list into videos and audios, and put it back together again at
1292 nsTArray
<RefPtr
<LocalMediaDevice
>> videos
;
1293 nsTArray
<RefPtr
<LocalMediaDevice
>> audios
;
1295 for (const auto& device
: devicesRef
) {
1296 MOZ_ASSERT(device
->Kind() == MediaDeviceKind::Videoinput
||
1297 device
->Kind() == MediaDeviceKind::Audioinput
);
1298 if (device
->Kind() == MediaDeviceKind::Videoinput
) {
1299 videos
.AppendElement(device
);
1300 } else if (device
->Kind() == MediaDeviceKind::Audioinput
) {
1301 audios
.AppendElement(device
);
1305 const char* badConstraint
= nullptr;
1306 bool needVideo
= IsOn(aConstraints
.mVideo
);
1307 bool needAudio
= IsOn(aConstraints
.mAudio
);
1309 if (needVideo
&& videos
.Length()) {
1310 badConstraint
= MediaConstraintsHelper::SelectSettings(
1311 NormalizedConstraints(GetInvariant(aConstraints
.mVideo
)), videos
,
1314 if (!badConstraint
&& needAudio
&& audios
.Length()) {
1315 badConstraint
= MediaConstraintsHelper::SelectSettings(
1316 NormalizedConstraints(GetInvariant(aConstraints
.mAudio
)), audios
,
1319 if (badConstraint
) {
1320 LOG("SelectSettings: bad constraint found! Calling error handler!");
1321 nsString constraint
;
1322 constraint
.AssignASCII(badConstraint
);
1324 new MediaMgrError(MediaMgrError::Name::OverconstrainedError
, "",
1329 if (!needVideo
== !videos
.Length() && !needAudio
== !audios
.Length()) {
1330 for (auto& video
: videos
) {
1331 devicesRef
.AppendElement(video
);
1333 for (auto& audio
: audios
) {
1334 devicesRef
.AppendElement(audio
);
1337 holder
.Resolve(devices
, __func__
);
1342 * Describes a requested task that handles response from the UI and sends
1343 * results back to the DOM.
1345 class GetUserMediaTask
{
1347 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GetUserMediaTask
)
1348 GetUserMediaTask(uint64_t aWindowID
, const ipc::PrincipalInfo
& aPrincipalInfo
,
1349 CallerType aCallerType
)
1350 : mPrincipalInfo(aPrincipalInfo
),
1351 mWindowID(aWindowID
),
1352 mCallerType(aCallerType
) {}
1354 virtual void Denied(MediaMgrError::Name aName
,
1355 const nsCString
& aMessage
= ""_ns
) = 0;
1357 virtual GetUserMediaStreamTask
* AsGetUserMediaStreamTask() { return nullptr; }
1358 virtual SelectAudioOutputTask
* AsSelectAudioOutputTask() { return nullptr; }
1360 uint64_t GetWindowID() const { return mWindowID
; }
1361 enum CallerType
CallerType() const { return mCallerType
; }
1363 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
1364 size_t amount
= aMallocSizeOf(this);
1365 // Assume mWindowListener is owned by MediaManager.
1366 // Assume mAudioDeviceListener and mVideoDeviceListener are owned by
1368 // Assume PrincipalInfo string buffers are shared.
1369 // Member types without support for accounting of pointees:
1370 // MozPromiseHolder, RefPtr<LocalMediaDevice>.
1371 // We don't have a good way to account for lambda captures for MozPromise
1377 virtual ~GetUserMediaTask() = default;
1379 // Call GetPrincipalKey again, if not private browing, this time with
1380 // persist = true, to promote deviceIds to persistent, in case they're not
1381 // already. Fire'n'forget.
1382 void PersistPrincipalKey() {
1383 if (IsPrincipalInfoPrivate(mPrincipalInfo
)) {
1386 media::GetPrincipalKey(mPrincipalInfo
, true)
1388 GetCurrentSerialEventTarget(), __func__
,
1389 [](const media::PrincipalKeyPromise::ResolveOrRejectValue
& aValue
) {
1390 if (aValue
.IsReject()) {
1391 LOG("Failed get Principal key. Persisting of deviceIds "
1398 // Thread-safe (object) principal of Window with ID mWindowID
1399 const ipc::PrincipalInfo mPrincipalInfo
;
1402 // The ID of the not-necessarily-toplevel inner Window relevant global
1403 // object of the MediaDevices on which getUserMedia() was called
1404 const uint64_t mWindowID
;
1405 // Whether the JS caller of getUserMedia() has system (subject) principal
1406 const enum CallerType mCallerType
;
1410 * Describes a requested task that handles response from the UI to a
1411 * getUserMedia() request and sends results back to content. If the request
1412 * is allowed and device initialization succeeds, then the MozPromise is
1413 * resolved with a DOMMediaStream having a track or tracks for the approved
1414 * device or devices.
1416 class GetUserMediaStreamTask final
: public GetUserMediaTask
{
1418 GetUserMediaStreamTask(
1419 const MediaStreamConstraints
& aConstraints
,
1420 MozPromiseHolder
<MediaManager::StreamPromise
>&& aHolder
,
1421 uint64_t aWindowID
, RefPtr
<GetUserMediaWindowListener
> aWindowListener
,
1422 RefPtr
<DeviceListener
> aAudioDeviceListener
,
1423 RefPtr
<DeviceListener
> aVideoDeviceListener
,
1424 const MediaEnginePrefs
& aPrefs
, const ipc::PrincipalInfo
& aPrincipalInfo
,
1425 enum CallerType aCallerType
, bool aShouldFocusSource
)
1426 : GetUserMediaTask(aWindowID
, aPrincipalInfo
, aCallerType
),
1427 mConstraints(aConstraints
),
1428 mHolder(std::move(aHolder
)),
1429 mWindowListener(std::move(aWindowListener
)),
1430 mAudioDeviceListener(std::move(aAudioDeviceListener
)),
1431 mVideoDeviceListener(std::move(aVideoDeviceListener
)),
1433 mShouldFocusSource(aShouldFocusSource
),
1434 mManager(MediaManager::GetInstance()) {}
1436 void Allowed(RefPtr
<LocalMediaDevice
> aAudioDevice
,
1437 RefPtr
<LocalMediaDevice
> aVideoDevice
) {
1438 MOZ_ASSERT(aAudioDevice
|| aVideoDevice
);
1439 mAudioDevice
= std::move(aAudioDevice
);
1440 mVideoDevice
= std::move(aVideoDevice
);
1441 // Reuse the same thread to save memory.
1442 MediaManager::Dispatch(
1443 NewRunnableMethod("GetUserMediaStreamTask::AllocateDevices", this,
1444 &GetUserMediaStreamTask::AllocateDevices
));
1447 GetUserMediaStreamTask
* AsGetUserMediaStreamTask() override
{ return this; }
1450 ~GetUserMediaStreamTask() override
{
1451 if (!mHolder
.IsEmpty()) {
1452 Fail(MediaMgrError::Name::NotAllowedError
);
1456 void Fail(MediaMgrError::Name aName
, const nsCString
& aMessage
= ""_ns
,
1457 const nsString
& aConstraint
= u
""_ns
) {
1458 mHolder
.Reject(MakeRefPtr
<MediaMgrError
>(aName
, aMessage
, aConstraint
),
1460 // We add a disabled listener to the StreamListeners array until accepted
1461 // If this was the only active MediaStream, remove the window from the list.
1462 NS_DispatchToMainThread(NS_NewRunnableFunction(
1463 "DeviceListener::Stop",
1464 [audio
= mAudioDeviceListener
, video
= mVideoDeviceListener
] {
1475 * Runs on a separate thread and is responsible for allocating devices.
1477 * Do not run this on the main thread.
1479 void AllocateDevices() {
1480 MOZ_ASSERT(!NS_IsMainThread());
1481 LOG("GetUserMediaStreamTask::AllocateDevices()");
1483 // Allocate a video or audio device and return a MediaStream via
1484 // PrepareDOMStream().
1487 const char* errorMsg
= nullptr;
1488 const char* badConstraint
= nullptr;
1491 auto& constraints
= GetInvariant(mConstraints
.mAudio
);
1492 rv
= mAudioDevice
->Allocate(constraints
, mPrefs
, mWindowID
,
1494 if (NS_FAILED(rv
)) {
1495 errorMsg
= "Failed to allocate audiosource";
1496 if (rv
== NS_ERROR_NOT_AVAILABLE
&& !badConstraint
) {
1497 nsTArray
<RefPtr
<LocalMediaDevice
>> devices
;
1498 devices
.AppendElement(mAudioDevice
);
1499 badConstraint
= MediaConstraintsHelper::SelectSettings(
1500 NormalizedConstraints(constraints
), devices
, mCallerType
);
1504 if (!errorMsg
&& mVideoDevice
) {
1505 auto& constraints
= GetInvariant(mConstraints
.mVideo
);
1506 rv
= mVideoDevice
->Allocate(constraints
, mPrefs
, mWindowID
,
1508 if (NS_FAILED(rv
)) {
1509 errorMsg
= "Failed to allocate videosource";
1510 if (rv
== NS_ERROR_NOT_AVAILABLE
&& !badConstraint
) {
1511 nsTArray
<RefPtr
<LocalMediaDevice
>> devices
;
1512 devices
.AppendElement(mVideoDevice
);
1513 badConstraint
= MediaConstraintsHelper::SelectSettings(
1514 NormalizedConstraints(constraints
), devices
, mCallerType
);
1517 mAudioDevice
->Deallocate();
1520 mVideoTrackingId
.emplace(mVideoDevice
->GetTrackingId());
1524 LOG("%s %" PRIu32
, errorMsg
, static_cast<uint32_t>(rv
));
1525 if (badConstraint
) {
1526 Fail(MediaMgrError::Name::OverconstrainedError
, ""_ns
,
1527 NS_ConvertUTF8toUTF16(badConstraint
));
1529 Fail(MediaMgrError::Name::NotReadableError
, nsCString(errorMsg
));
1531 NS_DispatchToMainThread(
1532 NS_NewRunnableFunction("MediaManager::SendPendingGUMRequest", []() {
1533 if (MediaManager
* manager
= MediaManager::GetIfExists()) {
1534 manager
->SendPendingGUMRequest();
1539 NS_DispatchToMainThread(
1540 NewRunnableMethod("GetUserMediaStreamTask::PrepareDOMStream", this,
1541 &GetUserMediaStreamTask::PrepareDOMStream
));
1545 void Denied(MediaMgrError::Name aName
, const nsCString
& aMessage
) override
{
1546 MOZ_ASSERT(NS_IsMainThread());
1547 Fail(aName
, aMessage
);
1550 const MediaStreamConstraints
& GetConstraints() { return mConstraints
; }
1552 void PrimeVoiceProcessing() {
1553 mPrimingStream
= MakeAndAddRef
<PrimingCubebVoiceInputStream
>();
1554 mPrimingStream
->Init();
1558 void PrepareDOMStream();
1560 class PrimingCubebVoiceInputStream
{
1561 class Listener final
: public CubebInputStream::Listener
{
1562 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Listener
, override
);
1565 ~Listener() = default;
1567 long DataCallback(const void*, long) override
{
1568 MOZ_CRASH("Unexpected data callback");
1570 void StateCallback(cubeb_state
) override
{}
1571 void DeviceChangedCallback() override
{}
1574 NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET(
1575 PrimingCubebVoiceInputStream
, mCubebThread
.GetEventTarget())
1579 mCubebThread
.GetEventTarget()->Dispatch(
1580 NS_NewRunnableFunction(__func__
, [this, self
= RefPtr(this)] {
1581 mCubebThread
.AssertOnCurrentThread();
1582 LOG("Priming voice processing with stream %p", this);
1583 TRACE("PrimingCubebVoiceInputStream::Init");
1584 const cubeb_devid default_device
= nullptr;
1585 const uint32_t mono
= 1;
1586 const uint32_t rate
= CubebUtils::PreferredSampleRate(false);
1587 const bool isVoice
= true;
1589 CubebInputStream::Create(default_device
, mono
, rate
, isVoice
,
1590 MakeRefPtr
<Listener
>().get());
1595 ~PrimingCubebVoiceInputStream() {
1596 mCubebThread
.AssertOnCurrentThread();
1597 LOG("Releasing primed voice processing stream %p", this);
1598 mCubebStream
= nullptr;
1601 const EventTargetCapability
<nsISerialEventTarget
> mCubebThread
=
1602 EventTargetCapability
<nsISerialEventTarget
>(
1603 TaskQueue::Create(CubebUtils::GetCubebOperationThread(),
1604 "PrimingCubebInputStream::mCubebThread")
1606 UniquePtr
<CubebInputStream
> mCubebStream
MOZ_GUARDED_BY(mCubebThread
);
1609 // Constraints derived from those passed to getUserMedia() but adjusted for
1610 // preferences, defaults, and security
1611 const MediaStreamConstraints mConstraints
;
1613 MozPromiseHolder
<MediaManager::StreamPromise
> mHolder
;
1614 // GetUserMediaWindowListener with which DeviceListeners are registered
1615 const RefPtr
<GetUserMediaWindowListener
> mWindowListener
;
1616 const RefPtr
<DeviceListener
> mAudioDeviceListener
;
1617 const RefPtr
<DeviceListener
> mVideoDeviceListener
;
1618 // MediaDevices are set when selected and Allowed() by the UI.
1619 RefPtr
<LocalMediaDevice
> mAudioDevice
;
1620 RefPtr
<LocalMediaDevice
> mVideoDevice
;
1621 RefPtr
<PrimingCubebVoiceInputStream
> mPrimingStream
;
1622 // Tracking id unique for a video frame source. Set when the corresponding
1623 // device has been allocated.
1624 Maybe
<TrackingId
> mVideoTrackingId
;
1625 // Copy of MediaManager::mPrefs
1626 const MediaEnginePrefs mPrefs
;
1627 // media.getusermedia.window.focus_source.enabled
1628 const bool mShouldFocusSource
;
1629 // The MediaManager is referenced at construction so that it won't be
1630 // created after its ShutdownBlocker would run.
1631 const RefPtr
<MediaManager
> mManager
;
1635 * Creates a MediaTrack, attaches a listener and resolves a MozPromise to
1636 * provide the stream to the DOM.
1638 * All of this must be done on the main thread!
1640 void GetUserMediaStreamTask::PrepareDOMStream() {
1641 MOZ_ASSERT(NS_IsMainThread());
1642 LOG("GetUserMediaStreamTask::PrepareDOMStream()");
1643 nsGlobalWindowInner
* window
=
1644 nsGlobalWindowInner::GetInnerWindowWithId(mWindowID
);
1646 // We're on main-thread, and the windowlist can only
1647 // be invalidated from the main-thread (see OnNavigation)
1648 if (!mManager
->IsWindowListenerStillActive(mWindowListener
)) {
1649 // This window is no longer live. mListener has already been removed.
1653 MediaTrackGraph::GraphDriverType graphDriverType
=
1654 mAudioDevice
? MediaTrackGraph::AUDIO_THREAD_DRIVER
1655 : MediaTrackGraph::SYSTEM_THREAD_DRIVER
;
1656 MediaTrackGraph
* mtg
= MediaTrackGraph::GetInstance(
1657 graphDriverType
, window
, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE
,
1658 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE
);
1660 auto domStream
= MakeRefPtr
<DOMMediaStream
>(window
);
1661 RefPtr
<LocalTrackSource
> audioTrackSource
;
1662 RefPtr
<LocalTrackSource
> videoTrackSource
;
1663 nsCOMPtr
<nsIPrincipal
> principal
;
1664 RefPtr
<PeerIdentity
> peerIdentity
= nullptr;
1665 if (!mConstraints
.mPeerIdentity
.IsEmpty()) {
1666 peerIdentity
= new PeerIdentity(mConstraints
.mPeerIdentity
);
1667 principal
= NullPrincipal::CreateWithInheritedAttributes(
1668 window
->GetExtantDoc()->NodePrincipal());
1670 principal
= window
->GetExtantDoc()->NodePrincipal();
1672 RefPtr
<GenericNonExclusivePromise
> firstFramePromise
;
1674 if (mAudioDevice
->GetMediaSource() == MediaSourceEnum::AudioCapture
) {
1675 // AudioCapture is a special case, here, in the sense that we're not
1676 // really using the audio source and the SourceMediaTrack, which acts
1677 // as placeholders. We re-route a number of tracks internally in the
1678 // MTG and mix them down instead.
1680 "MediaCaptureWindowState doesn't handle "
1681 "MediaSourceEnum::AudioCapture. This must be fixed with UX "
1682 "before shipping.");
1683 auto audioCaptureSource
= MakeRefPtr
<AudioCaptureTrackSource
>(
1684 principal
, window
, u
"Window audio capture"_ns
,
1685 mtg
->CreateAudioCaptureTrack(), peerIdentity
);
1686 audioTrackSource
= audioCaptureSource
;
1687 RefPtr
<MediaStreamTrack
> track
= new dom::AudioStreamTrack(
1688 window
, audioCaptureSource
->InputTrack(), audioCaptureSource
);
1689 domStream
->AddTrackInternal(track
);
1691 const nsString
& audioDeviceName
= mAudioDevice
->mName
;
1692 RefPtr
<MediaTrack
> track
;
1694 if (mAudioDevice
->IsFake()) {
1695 track
= mtg
->CreateSourceTrack(MediaSegment::AUDIO
);
1697 track
= AudioProcessingTrack::Create(mtg
);
1698 track
->Suspend(); // Microphone source resumes in SetTrack
1701 track
= mtg
->CreateSourceTrack(MediaSegment::AUDIO
);
1703 audioTrackSource
= new LocalTrackSource(
1704 principal
, audioDeviceName
, mAudioDeviceListener
,
1705 mAudioDevice
->GetMediaSource(), track
, peerIdentity
);
1706 MOZ_ASSERT(MediaManager::IsOn(mConstraints
.mAudio
));
1707 RefPtr
<MediaStreamTrack
> domTrack
= new dom::AudioStreamTrack(
1708 window
, track
, audioTrackSource
, dom::MediaStreamTrackState::Live
,
1709 false, GetInvariant(mConstraints
.mAudio
));
1710 domStream
->AddTrackInternal(domTrack
);
1714 const nsString
& videoDeviceName
= mVideoDevice
->mName
;
1715 RefPtr
<MediaTrack
> track
= mtg
->CreateSourceTrack(MediaSegment::VIDEO
);
1716 videoTrackSource
= new LocalTrackSource(
1717 principal
, videoDeviceName
, mVideoDeviceListener
,
1718 mVideoDevice
->GetMediaSource(), track
, peerIdentity
, *mVideoTrackingId
);
1719 MOZ_ASSERT(MediaManager::IsOn(mConstraints
.mVideo
));
1720 RefPtr
<MediaStreamTrack
> domTrack
= new dom::VideoStreamTrack(
1721 window
, track
, videoTrackSource
, dom::MediaStreamTrackState::Live
,
1722 false, GetInvariant(mConstraints
.mVideo
));
1723 domStream
->AddTrackInternal(domTrack
);
1724 switch (mVideoDevice
->GetMediaSource()) {
1725 case MediaSourceEnum::Browser
:
1726 case MediaSourceEnum::Screen
:
1727 case MediaSourceEnum::Window
:
1728 // Wait for first frame for screen-sharing devices, to ensure
1729 // with and height settings are available immediately, to pass wpt.
1730 firstFramePromise
= mVideoDevice
->Source()->GetFirstFramePromise();
1737 if (!domStream
|| (!audioTrackSource
&& !videoTrackSource
) ||
1738 sHasMainThreadShutdown
) {
1739 LOG("Returning error for getUserMedia() - no stream");
1742 MakeRefPtr
<MediaMgrError
>(
1743 MediaMgrError::Name::AbortError
,
1744 sHasMainThreadShutdown
? "In shutdown"_ns
: "No stream."_ns
),
1749 // Activate our device listeners. We'll call Start() on the source when we
1750 // get a callback that the MediaStream has started consuming. The listener
1751 // is freed when the page is invalidated (on navigation or close).
1752 if (mAudioDeviceListener
) {
1753 mWindowListener
->Activate(mAudioDeviceListener
, mAudioDevice
,
1754 std::move(audioTrackSource
));
1756 if (mVideoDeviceListener
) {
1757 mWindowListener
->Activate(mVideoDeviceListener
, mVideoDevice
,
1758 std::move(videoTrackSource
));
1761 // Dispatch to the media thread to ask it to start the sources, because that
1762 // can take a while.
1763 typedef DeviceListener::DeviceListenerPromise PromiseType
;
1764 AutoTArray
<RefPtr
<PromiseType
>, 2> promises
;
1765 if (mAudioDeviceListener
) {
1766 promises
.AppendElement(mAudioDeviceListener
->InitializeAsync());
1768 if (mVideoDeviceListener
) {
1769 promises
.AppendElement(mVideoDeviceListener
->InitializeAsync());
1771 PromiseType::All(GetMainThreadSerialEventTarget(), promises
)
1773 GetMainThreadSerialEventTarget(), __func__
,
1774 [manager
= mManager
, windowListener
= mWindowListener
,
1775 firstFramePromise
] {
1776 LOG("GetUserMediaStreamTask::PrepareDOMStream: starting success "
1777 "callback following InitializeAsync()");
1778 // Initiating and starting devices succeeded.
1779 windowListener
->ChromeAffectingStateChanged();
1780 manager
->SendPendingGUMRequest();
1781 if (!firstFramePromise
) {
1782 return DeviceListener::DeviceListenerPromise::CreateAndResolve(
1785 RefPtr
<DeviceListener::DeviceListenerPromise
> resolvePromise
=
1786 firstFramePromise
->Then(
1787 GetMainThreadSerialEventTarget(), __func__
,
1789 return DeviceListener::DeviceListenerPromise::
1790 CreateAndResolve(true, __func__
);
1792 [](nsresult aError
) {
1793 MOZ_ASSERT(NS_FAILED(aError
));
1794 if (aError
== NS_ERROR_UNEXPECTED
) {
1795 return DeviceListener::DeviceListenerPromise::
1797 MakeRefPtr
<MediaMgrError
>(
1798 MediaMgrError::Name::NotAllowedError
),
1801 MOZ_ASSERT(aError
== NS_ERROR_ABORT
);
1802 return DeviceListener::DeviceListenerPromise::
1803 CreateAndReject(MakeRefPtr
<MediaMgrError
>(
1804 MediaMgrError::Name::AbortError
,
1808 return resolvePromise
;
1810 [audio
= mAudioDeviceListener
,
1811 video
= mVideoDeviceListener
](RefPtr
<MediaMgrError
>&& aError
) {
1812 LOG("GetUserMediaStreamTask::PrepareDOMStream: starting failure "
1813 "callback following InitializeAsync()");
1820 return DeviceListener::DeviceListenerPromise::CreateAndReject(
1824 GetMainThreadSerialEventTarget(), __func__
,
1825 [holder
= std::move(mHolder
), domStream
, callerType
= mCallerType
,
1826 shouldFocus
= mShouldFocusSource
, videoDevice
= mVideoDevice
](
1827 const DeviceListener::DeviceListenerPromise::ResolveOrRejectValue
&
1829 if (aValue
.IsResolve()) {
1830 if (auto* mgr
= MediaManager::GetIfExists();
1831 mgr
&& !sHasMainThreadShutdown
&& videoDevice
&&
1832 callerType
== CallerType::NonSystem
&& shouldFocus
) {
1833 // Device was successfully started. Attempt to focus the
1835 MOZ_ALWAYS_SUCCEEDS(
1836 mgr
->mMediaThread
->Dispatch(NS_NewRunnableFunction(
1837 "GetUserMediaStreamTask::FocusOnSelectedSource",
1838 [videoDevice
= std::move(videoDevice
)] {
1839 nsresult rv
= videoDevice
->FocusOnSelectedSource();
1840 if (NS_FAILED(rv
)) {
1841 LOG("FocusOnSelectedSource failed");
1846 holder
.Resolve(domStream
, __func__
);
1848 holder
.Reject(aValue
.RejectValue(), __func__
);
1852 PersistPrincipalKey();
1856 * Describes a requested task that handles response from the UI to a
1857 * selectAudioOutput() request and sends results back to content. If the
1858 * request is allowed, then the MozPromise is resolved with a MediaDevice
1859 * for the approved device.
1861 class SelectAudioOutputTask final
: public GetUserMediaTask
{
1863 SelectAudioOutputTask(MozPromiseHolder
<LocalDevicePromise
>&& aHolder
,
1864 uint64_t aWindowID
, enum CallerType aCallerType
,
1865 const ipc::PrincipalInfo
& aPrincipalInfo
)
1866 : GetUserMediaTask(aWindowID
, aPrincipalInfo
, aCallerType
),
1867 mHolder(std::move(aHolder
)) {}
1869 void Allowed(RefPtr
<LocalMediaDevice
> aAudioOutput
) {
1870 MOZ_ASSERT(aAudioOutput
);
1871 mHolder
.Resolve(std::move(aAudioOutput
), __func__
);
1872 PersistPrincipalKey();
1875 void Denied(MediaMgrError::Name aName
, const nsCString
& aMessage
) override
{
1876 MOZ_ASSERT(NS_IsMainThread());
1877 Fail(aName
, aMessage
);
1880 SelectAudioOutputTask
* AsSelectAudioOutputTask() override
{ return this; }
1883 ~SelectAudioOutputTask() override
{
1884 if (!mHolder
.IsEmpty()) {
1885 Fail(MediaMgrError::Name::NotAllowedError
);
1889 void Fail(MediaMgrError::Name aName
, const nsCString
& aMessage
= ""_ns
) {
1890 mHolder
.Reject(MakeRefPtr
<MediaMgrError
>(aName
, aMessage
), __func__
);
1894 MozPromiseHolder
<LocalDevicePromise
> mHolder
;
1898 void MediaManager::GuessVideoDeviceGroupIDs(MediaDeviceSet
& aDevices
,
1899 const MediaDeviceSet
& aAudios
) {
1900 // Run the logic in a lambda to avoid duplication.
1901 auto updateGroupIdIfNeeded
= [&](RefPtr
<MediaDevice
>& aVideo
,
1902 const MediaDeviceKind aKind
) -> bool {
1903 MOZ_ASSERT(aVideo
->mKind
== MediaDeviceKind::Videoinput
);
1904 MOZ_ASSERT(aKind
== MediaDeviceKind::Audioinput
||
1905 aKind
== MediaDeviceKind::Audiooutput
);
1906 // This will store the new group id if a match is found.
1907 nsString newVideoGroupID
;
1908 // If the group id needs to be updated this will become true. It is
1909 // necessary when the new group id is an empty string. Without this extra
1910 // variable to signal the update, we would resort to test if
1911 // `newVideoGroupId` is empty. However,
1912 // that check does not work when the new group id is an empty string.
1913 bool updateGroupId
= false;
1914 for (const RefPtr
<MediaDevice
>& dev
: aAudios
) {
1915 if (dev
->mKind
!= aKind
) {
1918 if (!FindInReadable(aVideo
->mRawName
, dev
->mRawName
)) {
1921 if (newVideoGroupID
.IsEmpty()) {
1922 // This is only expected on first match. If that's the only match group
1923 // id will be updated to this one at the end of the loop.
1924 updateGroupId
= true;
1925 newVideoGroupID
= dev
->mRawGroupID
;
1927 // More than one device found, it is impossible to know which group id
1928 // is the correct one.
1929 updateGroupId
= false;
1930 newVideoGroupID
= u
""_ns
;
1934 if (updateGroupId
) {
1935 aVideo
= MediaDevice::CopyWithNewRawGroupId(aVideo
, newVideoGroupID
);
1941 for (RefPtr
<MediaDevice
>& video
: aDevices
) {
1942 if (video
->mKind
!= MediaDeviceKind::Videoinput
) {
1945 if (updateGroupIdIfNeeded(video
, MediaDeviceKind::Audioinput
)) {
1946 // GroupId has been updated, continue to the next video device
1949 // GroupId has not been updated, check among the outputs
1950 updateGroupIdIfNeeded(video
, MediaDeviceKind::Audiooutput
);
1956 // Class to hold the promise used to request device access and to resolve
1957 // even if |task| does not run, either because GeckoViewPermissionProcessChild
1958 // gets destroyed before ask-device-permission receives its
1959 // got-device-permission reply, or because the media thread is no longer
1960 // available. In either case, the process is shutting down so the result is
1961 // not important. Reject with a dummy error so the following Then-handler can
1962 // resolve with an empty set, so that callers do not need to handle rejection.
1963 class DeviceAccessRequestPromiseHolderWithFallback
1964 : public MozPromiseHolder
<MozPromise
<
1965 CamerasAccessStatus
, mozilla::ipc::ResponseRejectReason
, true>> {
1967 DeviceAccessRequestPromiseHolderWithFallback() = default;
1968 DeviceAccessRequestPromiseHolderWithFallback(
1969 DeviceAccessRequestPromiseHolderWithFallback
&&) = default;
1970 ~DeviceAccessRequestPromiseHolderWithFallback() {
1972 Reject(ipc::ResponseRejectReason::ChannelClosed
, __func__
);
1977 } // anonymous namespace
1979 MediaManager::DeviceEnumerationParams::DeviceEnumerationParams(
1980 dom::MediaSourceEnum aInputType
, DeviceType aType
,
1981 nsAutoCString aForcedDeviceName
)
1982 : mInputType(aInputType
),
1984 mForcedDeviceName(std::move(aForcedDeviceName
)) {
1985 MOZ_ASSERT(NS_IsMainThread());
1986 MOZ_ASSERT(mInputType
!= dom::MediaSourceEnum::Other
);
1987 MOZ_ASSERT_IF(!mForcedDeviceName
.IsEmpty(), mType
== DeviceType::Real
);
1990 MediaManager::VideoDeviceEnumerationParams::VideoDeviceEnumerationParams(
1991 dom::MediaSourceEnum aInputType
, DeviceType aType
,
1992 nsAutoCString aForcedDeviceName
, nsAutoCString aForcedMicrophoneName
)
1993 : DeviceEnumerationParams(aInputType
, aType
, std::move(aForcedDeviceName
)),
1994 mForcedMicrophoneName(std::move(aForcedMicrophoneName
)) {
1995 MOZ_ASSERT(NS_IsMainThread());
1996 MOZ_ASSERT_IF(!mForcedMicrophoneName
.IsEmpty(),
1997 mInputType
== dom::MediaSourceEnum::Camera
);
1998 MOZ_ASSERT_IF(!mForcedMicrophoneName
.IsEmpty(), mType
== DeviceType::Real
);
2001 MediaManager::EnumerationParams::EnumerationParams(
2002 EnumerationFlags aFlags
, Maybe
<VideoDeviceEnumerationParams
> aVideo
,
2003 Maybe
<DeviceEnumerationParams
> aAudio
)
2004 : mFlags(aFlags
), mVideo(std::move(aVideo
)), mAudio(std::move(aAudio
)) {
2005 MOZ_ASSERT(NS_IsMainThread());
2006 MOZ_ASSERT_IF(mVideo
, MediaEngineSource::IsVideo(mVideo
->mInputType
));
2007 MOZ_ASSERT_IF(mVideo
&& !mVideo
->mForcedDeviceName
.IsEmpty(),
2008 mVideo
->mInputType
== dom::MediaSourceEnum::Camera
);
2009 MOZ_ASSERT_IF(mVideo
&& mVideo
->mType
== DeviceType::Fake
,
2010 mVideo
->mInputType
== dom::MediaSourceEnum::Camera
);
2011 MOZ_ASSERT_IF(mAudio
, MediaEngineSource::IsAudio(mAudio
->mInputType
));
2012 MOZ_ASSERT_IF(mAudio
&& !mAudio
->mForcedDeviceName
.IsEmpty(),
2013 mAudio
->mInputType
== dom::MediaSourceEnum::Microphone
);
2014 MOZ_ASSERT_IF(mAudio
&& mAudio
->mType
== DeviceType::Fake
,
2015 mAudio
->mInputType
== dom::MediaSourceEnum::Microphone
);
2018 bool MediaManager::EnumerationParams::HasFakeCams() const {
2020 .map([](const auto& aDev
) { return aDev
.mType
== DeviceType::Fake
; })
2024 bool MediaManager::EnumerationParams::HasFakeMics() const {
2026 .map([](const auto& aDev
) { return aDev
.mType
== DeviceType::Fake
; })
2030 bool MediaManager::EnumerationParams::RealDeviceRequested() const {
2031 auto isReal
= [](const auto& aDev
) { return aDev
.mType
== DeviceType::Real
; };
2032 return mVideo
.map(isReal
).valueOr(false) ||
2033 mAudio
.map(isReal
).valueOr(false) ||
2034 mFlags
.contains(EnumerationFlag::EnumerateAudioOutputs
);
2037 MediaSourceEnum
MediaManager::EnumerationParams::VideoInputType() const {
2038 return mVideo
.map([](const auto& aDev
) { return aDev
.mInputType
; })
2039 .valueOr(MediaSourceEnum::Other
);
2042 MediaSourceEnum
MediaManager::EnumerationParams::AudioInputType() const {
2043 return mAudio
.map([](const auto& aDev
) { return aDev
.mInputType
; })
2044 .valueOr(MediaSourceEnum::Other
);
2047 /* static */ MediaManager::EnumerationParams
2048 MediaManager::CreateEnumerationParams(dom::MediaSourceEnum aVideoInputType
,
2049 dom::MediaSourceEnum aAudioInputType
,
2050 EnumerationFlags aFlags
) {
2051 MOZ_ASSERT(NS_IsMainThread());
2052 MOZ_ASSERT_IF(!MediaEngineSource::IsVideo(aVideoInputType
),
2053 aVideoInputType
== dom::MediaSourceEnum::Other
);
2054 MOZ_ASSERT_IF(!MediaEngineSource::IsAudio(aAudioInputType
),
2055 aAudioInputType
== dom::MediaSourceEnum::Other
);
2056 const bool forceFakes
= aFlags
.contains(EnumerationFlag::ForceFakes
);
2057 const bool fakeByPref
= Preferences::GetBool("media.navigator.streams.fake");
2058 Maybe
<VideoDeviceEnumerationParams
> videoParams
;
2059 Maybe
<DeviceEnumerationParams
> audioParams
;
2060 nsAutoCString audioDev
;
2061 bool audioDevRead
= false;
2062 constexpr const char* VIDEO_DEV_NAME
= "media.video_loopback_dev";
2063 constexpr const char* AUDIO_DEV_NAME
= "media.audio_loopback_dev";
2064 const auto ensureDev
= [](const char* aPref
, nsAutoCString
* aLoopDev
,
2073 if (NS_FAILED(Preferences::GetCString(aPref
, *aLoopDev
))) {
2074 // Ensure we fall back to an empty string if reading the pref failed.
2075 aLoopDev
->SetIsVoid(true);
2078 if (MediaEngineSource::IsVideo(aVideoInputType
)) {
2079 nsAutoCString videoDev
;
2080 DeviceType type
= DeviceType::Real
;
2081 if (aVideoInputType
== MediaSourceEnum::Camera
) {
2082 // Fake and loopback devices are supported for only Camera.
2084 type
= DeviceType::Fake
;
2086 ensureDev(VIDEO_DEV_NAME
, &videoDev
, nullptr);
2087 // Loopback prefs take precedence over fake prefs
2088 if (fakeByPref
&& videoDev
.IsEmpty()) {
2089 type
= DeviceType::Fake
;
2091 // For groupId correlation we need the audio device name.
2092 ensureDev(AUDIO_DEV_NAME
, &audioDev
, &audioDevRead
);
2096 videoParams
= Some(VideoDeviceEnumerationParams(aVideoInputType
, type
,
2097 videoDev
, audioDev
));
2099 if (MediaEngineSource::IsAudio(aAudioInputType
)) {
2100 nsAutoCString realAudioDev
;
2101 DeviceType type
= DeviceType::Real
;
2102 if (aAudioInputType
== MediaSourceEnum::Microphone
) {
2103 // Fake and loopback devices are supported for only Microphone.
2105 type
= DeviceType::Fake
;
2107 ensureDev(AUDIO_DEV_NAME
, &audioDev
, &audioDevRead
);
2108 // Loopback prefs take precedence over fake prefs
2109 if (fakeByPref
&& audioDev
.IsEmpty()) {
2110 type
= DeviceType::Fake
;
2112 realAudioDev
= audioDev
;
2117 Some(DeviceEnumerationParams(aAudioInputType
, type
, realAudioDev
));
2119 return EnumerationParams(aFlags
, videoParams
, audioParams
);
2122 RefPtr
<DeviceSetPromise
>
2123 MediaManager::MaybeRequestPermissionAndEnumerateRawDevices(
2124 EnumerationParams aParams
) {
2125 MOZ_ASSERT(NS_IsMainThread());
2126 MOZ_ASSERT(aParams
.mVideo
.isSome() || aParams
.mAudio
.isSome() ||
2127 aParams
.mFlags
.contains(EnumerationFlag::EnumerateAudioOutputs
));
2129 LOG("%s: aVideoInputType=%" PRIu8
", aAudioInputType=%" PRIu8
, __func__
,
2130 static_cast<uint8_t>(aParams
.VideoInputType()),
2131 static_cast<uint8_t>(aParams
.AudioInputType()));
2133 if (sHasMainThreadShutdown
) {
2134 // The media thread is no longer available but the result will not be
2136 return DeviceSetPromise::CreateAndResolve(
2137 new MediaDeviceSetRefCnt(),
2138 "MaybeRequestPermissionAndEnumerateRawDevices: sync shutdown");
2141 const bool hasVideo
= aParams
.mVideo
.isSome();
2142 const bool hasAudio
= aParams
.mAudio
.isSome();
2143 const bool hasAudioOutput
=
2144 aParams
.mFlags
.contains(EnumerationFlag::EnumerateAudioOutputs
);
2145 const bool hasFakeCams
= aParams
.HasFakeCams();
2146 const bool hasFakeMics
= aParams
.HasFakeMics();
2147 // True if at least one of video input or audio input is a real device
2148 // or there is audio output.
2149 const bool realDeviceRequested
= (!hasFakeCams
&& hasVideo
) ||
2150 (!hasFakeMics
&& hasAudio
) || hasAudioOutput
;
2152 using NativePromise
=
2153 MozPromise
<CamerasAccessStatus
, mozilla::ipc::ResponseRejectReason
,
2154 /* IsExclusive = */ true>;
2155 RefPtr
<NativePromise
> deviceAccessPromise
;
2156 if (realDeviceRequested
&&
2157 aParams
.mFlags
.contains(EnumerationFlag::AllowPermissionRequest
) &&
2158 Preferences::GetBool("media.navigator.permission.device", false)) {
2159 // Need to ask permission to retrieve list of all devices;
2160 // notify frontend observer and wait for callback notification to post
2162 const char16_t
* const type
=
2163 (aParams
.VideoInputType() != MediaSourceEnum::Camera
) ? u
"audio"
2164 : (aParams
.AudioInputType() != MediaSourceEnum::Microphone
) ? u
"video"
2166 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
2167 DeviceAccessRequestPromiseHolderWithFallback deviceAccessPromiseHolder
;
2168 deviceAccessPromise
= deviceAccessPromiseHolder
.Ensure(__func__
);
2169 RefPtr task
= NS_NewRunnableFunction(
2170 __func__
, [holder
= std::move(deviceAccessPromiseHolder
)]() mutable {
2171 holder
.Resolve(CamerasAccessStatus::Granted
,
2172 "getUserMedia:got-device-permission");
2174 obs
->NotifyObservers(static_cast<nsIRunnable
*>(task
),
2175 "getUserMedia:ask-device-permission", type
);
2176 } else if (realDeviceRequested
&& hasVideo
&&
2177 aParams
.VideoInputType() == MediaSourceEnum::Camera
) {
2178 ipc::PBackgroundChild
* backgroundChild
=
2179 ipc::BackgroundChild::GetOrCreateForCurrentThread();
2180 deviceAccessPromise
= backgroundChild
->SendRequestCameraAccess(
2181 aParams
.mFlags
.contains(EnumerationFlag::AllowPermissionRequest
));
2184 if (!deviceAccessPromise
) {
2185 // No device access request needed. We can proceed directly, but we still
2186 // need to update camera availability, because the camera engine is always
2187 // created together with the WebRTC backend, which is done because
2188 // devicechange events must work before prompting in cases where persistent
2189 // permission has already been given. Making a request to camera access not
2190 // allowing a permission request does exactly what we need in this case.
2191 ipc::PBackgroundChild
* backgroundChild
=
2192 ipc::BackgroundChild::GetOrCreateForCurrentThread();
2193 deviceAccessPromise
= backgroundChild
->SendRequestCameraAccess(false);
2196 return deviceAccessPromise
->Then(
2197 GetCurrentSerialEventTarget(), __func__
,
2198 [this, self
= RefPtr(this), aParams
= std::move(aParams
)](
2199 NativePromise::ResolveOrRejectValue
&& aValue
) mutable {
2200 if (sHasMainThreadShutdown
) {
2201 return DeviceSetPromise::CreateAndResolve(
2202 new MediaDeviceSetRefCnt(),
2203 "MaybeRequestPermissionAndEnumerateRawDevices: async shutdown");
2206 if (aValue
.IsReject()) {
2207 // IPC failure probably means we're in shutdown. Resolve with
2208 // an empty set, so that callers do not need to handle rejection.
2209 return DeviceSetPromise::CreateAndResolve(
2210 new MediaDeviceSetRefCnt(),
2211 "MaybeRequestPermissionAndEnumerateRawDevices: ipc failure");
2214 if (auto v
= aValue
.ResolveValue();
2215 v
== CamerasAccessStatus::Error
||
2216 v
== CamerasAccessStatus::Rejected
) {
2217 LOG("Request to camera access %s",
2218 v
== CamerasAccessStatus::Rejected
? "was rejected" : "failed");
2219 if (v
== CamerasAccessStatus::Error
) {
2220 NS_WARNING("Failed to request camera access");
2222 return DeviceSetPromise::CreateAndReject(
2223 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
2224 "MaybeRequestPermissionAndEnumerateRawDevices: camera access "
2228 if (aParams
.VideoInputType() == MediaSourceEnum::Camera
&&
2229 aParams
.mFlags
.contains(EnumerationFlag::AllowPermissionRequest
) &&
2230 aValue
.ResolveValue() == CamerasAccessStatus::Granted
) {
2231 EnsureNoPlaceholdersInDeviceCache();
2234 // We have to nest this, unfortunately, since we have no guarantees that
2235 // mMediaThread is alive. If we'd reject due to shutdown above, and have
2236 // the below async operation in a Then handler on the media thread the
2237 // Then handler would fail to dispatch and trip an assert on
2238 // destruction, for instance.
2240 mMediaThread
, __func__
, [aParams
= std::move(aParams
)]() mutable {
2241 return DeviceSetPromise::CreateAndResolve(
2242 EnumerateRawDevices(std::move(aParams
)),
2243 "MaybeRequestPermissionAndEnumerateRawDevices: success");
2249 * EnumerateRawDevices - Enumerate a list of audio & video devices that
2250 * satisfy passed-in constraints. List contains raw id's.
2253 /* static */ RefPtr
<MediaManager::MediaDeviceSetRefCnt
>
2254 MediaManager::EnumerateRawDevices(EnumerationParams aParams
) {
2255 MOZ_ASSERT(IsInMediaThread());
2256 // Only enumerate what's asked for, and only fake cams and mics.
2257 RefPtr
<MediaEngine
> fakeBackend
, realBackend
;
2258 if (aParams
.HasFakeCams() || aParams
.HasFakeMics()) {
2259 fakeBackend
= new MediaEngineFake();
2261 if (aParams
.RealDeviceRequested()) {
2262 MediaManager
* manager
= MediaManager::GetIfExists();
2263 MOZ_RELEASE_ASSERT(manager
, "Must exist while media thread is alive");
2264 realBackend
= manager
->GetBackend();
2267 RefPtr
<MediaEngine
> videoBackend
;
2268 RefPtr
<MediaEngine
> audioBackend
;
2269 Maybe
<MediaDeviceSet
> micsOfVideoBackend
;
2270 Maybe
<MediaDeviceSet
> speakers
;
2271 RefPtr devices
= new MediaDeviceSetRefCnt();
2273 // Enumerate microphones first, then cameras, then speakers, since
2274 // the enumerateDevices() algorithm expects them listed in that order.
2275 if (const auto& audio
= aParams
.mAudio
; audio
.isSome()) {
2276 audioBackend
= aParams
.HasFakeMics() ? fakeBackend
: realBackend
;
2277 MediaDeviceSet audios
;
2278 LOG("EnumerateRawDevices: Getting audio sources with %s backend",
2279 audioBackend
== fakeBackend
? "fake" : "real");
2280 GetMediaDevices(audioBackend
, audio
->mInputType
, audios
,
2281 audio
->mForcedDeviceName
.get());
2282 if (audio
->mInputType
== MediaSourceEnum::Microphone
&&
2283 audioBackend
== videoBackend
) {
2284 micsOfVideoBackend
.emplace();
2285 micsOfVideoBackend
->AppendElements(audios
);
2287 devices
->AppendElements(std::move(audios
));
2289 if (const auto& video
= aParams
.mVideo
; video
.isSome()) {
2290 videoBackend
= aParams
.HasFakeCams() ? fakeBackend
: realBackend
;
2291 MediaDeviceSet videos
;
2292 LOG("EnumerateRawDevices: Getting video sources with %s backend",
2293 videoBackend
== fakeBackend
? "fake" : "real");
2294 GetMediaDevices(videoBackend
, video
->mInputType
, videos
,
2295 video
->mForcedDeviceName
.get());
2296 devices
->AppendElements(std::move(videos
));
2298 if (aParams
.mFlags
.contains(EnumerationFlag::EnumerateAudioOutputs
)) {
2299 MediaDeviceSet outputs
;
2300 MOZ_ASSERT(realBackend
);
2301 realBackend
->EnumerateDevices(MediaSourceEnum::Other
,
2302 MediaSinkEnum::Speaker
, &outputs
);
2303 speakers
= Some(MediaDeviceSet());
2304 speakers
->AppendElements(outputs
);
2305 devices
->AppendElements(std::move(outputs
));
2307 if (aParams
.VideoInputType() == MediaSourceEnum::Camera
) {
2308 MediaDeviceSet audios
;
2309 LOG("EnumerateRawDevices: Getting audio sources with %s backend for "
2310 "groupId correlation",
2311 videoBackend
== fakeBackend
? "fake" : "real");
2312 // We need to correlate cameras with audio groupIds. We use the backend of
2313 // the camera to always do correlation on devices in the same scope. If we
2314 // don't do this, video-only getUserMedia will not apply groupId constraints
2315 // to the same set of groupIds as gets returned by enumerateDevices.
2316 if (micsOfVideoBackend
.isSome()) {
2317 // Microphones from the same backend used for the cameras have
2318 // already been enumerated. Avoid doing it again.
2319 MOZ_ASSERT(aParams
.mVideo
->mForcedMicrophoneName
==
2320 aParams
.mAudio
->mForcedDeviceName
);
2321 audios
.AppendElements(micsOfVideoBackend
.extract());
2323 GetMediaDevices(videoBackend
, MediaSourceEnum::Microphone
, audios
,
2324 aParams
.mVideo
->mForcedMicrophoneName
.get());
2326 if (videoBackend
== realBackend
) {
2327 // When using the real backend for video, there could also be
2328 // speakers to correlate with. There are no fake speakers.
2329 if (speakers
.isSome()) {
2330 // Speakers have already been enumerated. Avoid doing it again.
2331 audios
.AppendElements(speakers
.extract());
2333 realBackend
->EnumerateDevices(MediaSourceEnum::Other
,
2334 MediaSinkEnum::Speaker
, &audios
);
2337 GuessVideoDeviceGroupIDs(*devices
, audios
);
2343 RefPtr
<ConstDeviceSetPromise
> MediaManager::GetPhysicalDevices() {
2344 MOZ_ASSERT(NS_IsMainThread());
2345 if (mPhysicalDevices
) {
2346 return ConstDeviceSetPromise::CreateAndResolve(mPhysicalDevices
, __func__
);
2348 if (mPendingDevicesPromises
) {
2349 // Enumeration is already in progress.
2350 return mPendingDevicesPromises
->AppendElement()->Ensure(__func__
);
2352 mPendingDevicesPromises
=
2353 new Refcountable
<nsTArray
<MozPromiseHolder
<ConstDeviceSetPromise
>>>;
2354 MaybeRequestPermissionAndEnumerateRawDevices(
2355 CreateEnumerationParams(MediaSourceEnum::Camera
,
2356 MediaSourceEnum::Microphone
,
2357 EnumerationFlag::EnumerateAudioOutputs
))
2359 GetCurrentSerialEventTarget(), __func__
,
2360 [self
= RefPtr(this), this, promises
= mPendingDevicesPromises
](
2361 RefPtr
<MediaDeviceSetRefCnt
> aDevices
) mutable {
2362 for (auto& promiseHolder
: *promises
) {
2363 promiseHolder
.Resolve(aDevices
, __func__
);
2365 // mPendingDevicesPromises may have changed if devices have changed.
2366 if (promises
== mPendingDevicesPromises
) {
2367 mPendingDevicesPromises
= nullptr;
2368 mPhysicalDevices
= std::move(aDevices
);
2371 [](RefPtr
<MediaMgrError
>&& reason
) {
2372 MOZ_ASSERT_UNREACHABLE(
2373 "MaybeRequestPermissionAndEnumerateRawDevices does not reject");
2376 return mPendingDevicesPromises
->AppendElement()->Ensure(__func__
);
2379 MediaManager::MediaManager(already_AddRefed
<TaskQueue
> aMediaThread
)
2380 : mMediaThread(aMediaThread
), mBackend(nullptr) {
2381 mPrefs
.mFreq
= 1000; // 1KHz test tone
2382 mPrefs
.mWidth
= 0; // adaptive default
2383 mPrefs
.mHeight
= 0; // adaptive default
2384 mPrefs
.mFPS
= MediaEnginePrefs::DEFAULT_VIDEO_FPS
;
2385 mPrefs
.mUsePlatformProcessing
= false;
2386 mPrefs
.mAecOn
= false;
2387 mPrefs
.mUseAecMobile
= false;
2388 mPrefs
.mAgcOn
= false;
2389 mPrefs
.mHPFOn
= false;
2390 mPrefs
.mNoiseOn
= false;
2391 mPrefs
.mTransientOn
= false;
2392 mPrefs
.mAgc2Forced
= false;
2393 mPrefs
.mExpectDrift
= -1; // auto
2396 webrtc::AudioProcessing::Config::GainController1::Mode::kAdaptiveDigital
;
2398 webrtc::AudioProcessing::Config::NoiseSuppression::Level::kModerate
;
2403 mPrefs
.mChannels
= 0; // max channels default
2405 nsCOMPtr
<nsIPrefService
> prefs
=
2406 do_GetService("@mozilla.org/preferences-service;1", &rv
);
2407 if (NS_SUCCEEDED(rv
)) {
2408 nsCOMPtr
<nsIPrefBranch
> branch
= do_QueryInterface(prefs
);
2410 GetPrefs(branch
, nullptr);
2415 NS_IMPL_ISUPPORTS(MediaManager
, nsIMediaManagerService
, nsIMemoryReporter
,
2419 StaticRefPtr
<MediaManager
> MediaManager::sSingleton
;
2423 bool MediaManager::IsInMediaThread() {
2424 return sSingleton
&& sSingleton
->mMediaThread
->IsOnCurrentThread();
2428 template <typename Function
>
2429 static void ForeachObservedPref(const Function
& aFunction
) {
2430 aFunction("media.navigator.video.default_width"_ns
);
2431 aFunction("media.navigator.video.default_height"_ns
);
2432 aFunction("media.navigator.video.default_fps"_ns
);
2433 aFunction("media.navigator.audio.fake_frequency"_ns
);
2434 aFunction("media.audio_loopback_dev"_ns
);
2435 aFunction("media.video_loopback_dev"_ns
);
2436 aFunction("media.getusermedia.fake-camera-name"_ns
);
2438 aFunction("media.getusermedia.audio.processing.aec.enabled"_ns
);
2439 aFunction("media.getusermedia.audio.processing.aec"_ns
);
2440 aFunction("media.getusermedia.audio.processing.agc.enabled"_ns
);
2441 aFunction("media.getusermedia.audio.processing.agc"_ns
);
2442 aFunction("media.getusermedia.audio.processing.hpf.enabled"_ns
);
2443 aFunction("media.getusermedia.audio.processing.noise.enabled"_ns
);
2444 aFunction("media.getusermedia.audio.processing.noise"_ns
);
2445 aFunction("media.getusermedia.audio.max_channels"_ns
);
2446 aFunction("media.navigator.streams.fake"_ns
);
2450 // NOTE: never NS_DispatchAndSpinEventLoopUntilComplete to the MediaManager
2451 // thread from the MainThread, as we NS_DispatchAndSpinEventLoopUntilComplete to
2452 // MainThread from MediaManager thread.
2454 // Guaranteed never to return nullptr.
2456 MediaManager
* MediaManager::Get() {
2457 MOZ_ASSERT(NS_IsMainThread());
2460 static int timesCreated
= 0;
2462 MOZ_RELEASE_ASSERT(timesCreated
== 1);
2464 RefPtr
<TaskQueue
> mediaThread
= TaskQueue::Create(
2465 GetMediaThreadPool(MediaThreadType::SUPERVISOR
), "MediaManager");
2466 LOG("New Media thread for gum");
2468 sSingleton
= new MediaManager(mediaThread
.forget());
2470 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
2472 obs
->AddObserver(sSingleton
, "last-pb-context-exited", false);
2473 obs
->AddObserver(sSingleton
, "getUserMedia:got-device-permission", false);
2474 obs
->AddObserver(sSingleton
, "getUserMedia:privileged:allow", false);
2475 obs
->AddObserver(sSingleton
, "getUserMedia:response:allow", false);
2476 obs
->AddObserver(sSingleton
, "getUserMedia:response:deny", false);
2477 obs
->AddObserver(sSingleton
, "getUserMedia:response:noOSPermission",
2479 obs
->AddObserver(sSingleton
, "getUserMedia:revoke", false);
2480 obs
->AddObserver(sSingleton
, "getUserMedia:muteVideo", false);
2481 obs
->AddObserver(sSingleton
, "getUserMedia:unmuteVideo", false);
2482 obs
->AddObserver(sSingleton
, "getUserMedia:muteAudio", false);
2483 obs
->AddObserver(sSingleton
, "getUserMedia:unmuteAudio", false);
2484 obs
->AddObserver(sSingleton
, "application-background", false);
2485 obs
->AddObserver(sSingleton
, "application-foreground", false);
2487 // else MediaManager won't work properly and will leak (see bug 837874)
2488 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
2490 ForeachObservedPref([&](const nsLiteralCString
& aPrefName
) {
2491 prefs
->AddObserver(aPrefName
, sSingleton
, false);
2494 RegisterStrongMemoryReporter(sSingleton
);
2496 // Prepare async shutdown
2498 class Blocker
: public media::ShutdownBlocker
{
2501 : media::ShutdownBlocker(
2502 u
"Media shutdown: blocking on media thread"_ns
) {}
2504 NS_IMETHOD
BlockShutdown(nsIAsyncShutdownClient
*) override
{
2505 MOZ_RELEASE_ASSERT(MediaManager::GetIfExists());
2506 MediaManager::GetIfExists()->Shutdown();
2511 sSingleton
->mShutdownBlocker
= new Blocker();
2512 nsresult rv
= media::MustGetShutdownBarrier()->AddBlocker(
2513 sSingleton
->mShutdownBlocker
, NS_LITERAL_STRING_FROM_CSTRING(__FILE__
),
2515 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
));
2521 MediaManager
* MediaManager::GetIfExists() {
2522 MOZ_ASSERT(NS_IsMainThread() || IsInMediaThread());
2527 already_AddRefed
<MediaManager
> MediaManager::GetInstance() {
2528 // so we can have non-refcounted getters
2529 RefPtr
<MediaManager
> service
= MediaManager::Get();
2530 return service
.forget();
2533 media::Parent
<media::NonE10s
>* MediaManager::GetNonE10sParent() {
2534 if (!mNonE10sParent
) {
2535 mNonE10sParent
= new media::Parent
<media::NonE10s
>();
2537 return mNonE10sParent
;
2541 void MediaManager::Dispatch(already_AddRefed
<Runnable
> task
) {
2542 MOZ_ASSERT(NS_IsMainThread());
2543 if (sHasMainThreadShutdown
) {
2544 // Can't safely delete task here since it may have items with specific
2545 // thread-release requirements.
2546 // XXXkhuey well then who is supposed to delete it?! We don't signal
2547 // that we failed ...
2551 NS_ASSERTION(Get(), "MediaManager singleton?");
2552 NS_ASSERTION(Get()->mMediaThread
, "No thread yet");
2553 MOZ_ALWAYS_SUCCEEDS(Get()->mMediaThread
->Dispatch(std::move(task
)));
2556 template <typename MozPromiseType
, typename FunctionType
>
2558 RefPtr
<MozPromiseType
> MediaManager::Dispatch(StaticString aName
,
2559 FunctionType
&& aFunction
) {
2560 MozPromiseHolder
<MozPromiseType
> holder
;
2561 RefPtr
<MozPromiseType
> promise
= holder
.Ensure(aName
);
2562 MediaManager::Dispatch(NS_NewRunnableFunction(
2563 aName
, [h
= std::move(holder
), func
= std::forward
<FunctionType
>(
2564 aFunction
)]() mutable { func(h
); }));
2569 nsresult
MediaManager::NotifyRecordingStatusChange(
2570 nsPIDOMWindowInner
* aWindow
) {
2571 NS_ENSURE_ARG(aWindow
);
2573 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
2576 "Could not get the Observer service for GetUserMedia recording "
2578 return NS_ERROR_FAILURE
;
2581 auto props
= MakeRefPtr
<nsHashPropertyBag
>();
2584 nsCOMPtr
<nsIURI
> docURI
= aWindow
->GetDocumentURI();
2585 NS_ENSURE_TRUE(docURI
, NS_ERROR_FAILURE
);
2587 nsresult rv
= docURI
->GetSpec(pageURL
);
2588 NS_ENSURE_SUCCESS(rv
, rv
);
2590 NS_ConvertUTF8toUTF16
requestURL(pageURL
);
2592 props
->SetPropertyAsAString(u
"requestURL"_ns
, requestURL
);
2593 props
->SetPropertyAsInterface(u
"window"_ns
, aWindow
);
2595 obs
->NotifyObservers(static_cast<nsIPropertyBag2
*>(props
),
2596 "recording-device-events", nullptr);
2597 LOG("Sent recording-device-events for url '%s'", pageURL
.get());
2602 void MediaManager::DeviceListChanged() {
2603 MOZ_ASSERT(NS_IsMainThread());
2604 if (sHasMainThreadShutdown
) {
2607 // Invalidate immediately to provide an up-to-date device list for future
2608 // enumerations on platforms with sane device-list-changed events.
2609 InvalidateDeviceCache();
2611 // Wait 200 ms, because
2612 // A) on some Windows machines, if we call EnumerateRawDevices immediately
2613 // after receiving devicechange event, we'd get an outdated devices list.
2614 // B) Waiting helps coalesce multiple calls on us into one, which can happen
2615 // if a device with both audio input and output is attached or removed.
2616 // We want to react & fire a devicechange event only once in that case.
2618 // The wait is extended if another hardware device-list-changed notification
2619 // is received to provide the full 200ms for EnumerateRawDevices().
2620 if (mDeviceChangeTimer
) {
2621 mDeviceChangeTimer
->Cancel();
2623 mDeviceChangeTimer
= MakeRefPtr
<MediaTimer
<TimeStamp
>>();
2625 // However, if this would cause a delay of over 1000ms in handling the
2626 // oldest unhandled event, then respond now and set the timer to run
2627 // EnumerateRawDevices() again in 200ms.
2628 auto now
= TimeStamp::NowLoRes();
2629 auto enumerateDelay
= TimeDuration::FromMilliseconds(200);
2630 auto coalescenceLimit
= TimeDuration::FromMilliseconds(1000) - enumerateDelay
;
2631 if (!mUnhandledDeviceChangeTime
) {
2632 mUnhandledDeviceChangeTime
= now
;
2633 } else if (now
- mUnhandledDeviceChangeTime
> coalescenceLimit
) {
2634 HandleDeviceListChanged();
2635 mUnhandledDeviceChangeTime
= now
;
2637 RefPtr
<MediaManager
> self
= this;
2638 mDeviceChangeTimer
->WaitFor(enumerateDelay
, __func__
)
2640 GetCurrentSerialEventTarget(), __func__
,
2642 // Invalidate again for the sake of platforms with inconsistent
2643 // timing between device-list-changed notification and enumeration.
2644 InvalidateDeviceCache();
2646 mUnhandledDeviceChangeTime
= TimeStamp();
2647 HandleDeviceListChanged();
2649 [] { /* Timer was canceled by us, or we're in shutdown. */ });
2652 void MediaManager::EnsureNoPlaceholdersInDeviceCache() {
2653 MOZ_ASSERT(NS_IsMainThread());
2655 if (mPhysicalDevices
) {
2656 // Invalidate the list if there is a placeholder
2657 for (const auto& device
: *mPhysicalDevices
) {
2658 if (device
->mIsPlaceholder
) {
2659 InvalidateDeviceCache();
2666 void MediaManager::InvalidateDeviceCache() {
2667 MOZ_ASSERT(NS_IsMainThread());
2669 mPhysicalDevices
= nullptr;
2670 // Disconnect any in-progress enumeration, which may now be out of date,
2671 // from updating mPhysicalDevices or resolving future device request
2673 mPendingDevicesPromises
= nullptr;
2676 void MediaManager::HandleDeviceListChanged() {
2677 mDeviceListChangeEvent
.Notify();
2679 GetPhysicalDevices()->Then(
2680 GetCurrentSerialEventTarget(), __func__
,
2681 [self
= RefPtr(this), this](RefPtr
<const MediaDeviceSetRefCnt
> aDevices
) {
2682 if (!MediaManager::GetIfExists()) {
2686 nsTHashSet
<nsString
> deviceIDs
;
2687 for (const auto& device
: *aDevices
) {
2688 deviceIDs
.Insert(device
->mRawID
);
2690 // For any real removed cameras or microphones, notify their
2691 // listeners cleanly that the source has stopped, so JS knows and
2692 // usage indicators update.
2693 // First collect the listeners in an array to stop them after
2694 // iterating the hashtable. The StopRawID() method indirectly
2695 // modifies the mActiveWindows and would assert-crash if the
2696 // iterator were active while the table is being enumerated.
2697 const auto windowListeners
= ToArray(mActiveWindows
.Values());
2698 for (const RefPtr
<GetUserMediaWindowListener
>& l
: windowListeners
) {
2699 const auto activeDevices
= l
->GetDevices();
2700 for (const RefPtr
<LocalMediaDevice
>& device
: *activeDevices
) {
2701 if (device
->IsFake()) {
2704 MediaSourceEnum mediaSource
= device
->GetMediaSource();
2705 if (mediaSource
!= MediaSourceEnum::Microphone
&&
2706 mediaSource
!= MediaSourceEnum::Camera
) {
2709 if (!deviceIDs
.Contains(device
->RawID())) {
2710 // Device has been removed
2711 l
->StopRawID(device
->RawID());
2716 [](RefPtr
<MediaMgrError
>&& reason
) {
2717 MOZ_ASSERT_UNREACHABLE("EnumerateRawDevices does not reject");
2721 size_t MediaManager::AddTaskAndGetCount(uint64_t aWindowID
,
2722 const nsAString
& aCallID
,
2723 RefPtr
<GetUserMediaTask
> aTask
) {
2724 // Store the task w/callbacks.
2725 mActiveCallbacks
.InsertOrUpdate(aCallID
, std::move(aTask
));
2727 // Add a WindowID cross-reference so OnNavigation can tear things down
2728 nsTArray
<nsString
>* const array
= mCallIds
.GetOrInsertNew(aWindowID
);
2729 array
->AppendElement(aCallID
);
2731 return array
->Length();
2734 RefPtr
<GetUserMediaTask
> MediaManager::TakeGetUserMediaTask(
2735 const nsAString
& aCallID
) {
2736 RefPtr
<GetUserMediaTask
> task
;
2737 mActiveCallbacks
.Remove(aCallID
, getter_AddRefs(task
));
2741 nsTArray
<nsString
>* array
;
2742 mCallIds
.Get(task
->GetWindowID(), &array
);
2744 array
->RemoveElement(aCallID
);
2748 void MediaManager::NotifyAllowed(const nsString
& aCallID
,
2749 const LocalMediaDeviceSet
& aDevices
) {
2750 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
2751 nsCOMPtr
<nsIMutableArray
> devicesCopy
= nsArray::Create();
2752 for (const auto& device
: aDevices
) {
2753 nsresult rv
= devicesCopy
->AppendElement(device
);
2754 if (NS_WARN_IF(NS_FAILED(rv
))) {
2755 obs
->NotifyObservers(nullptr, "getUserMedia:response:deny",
2760 obs
->NotifyObservers(devicesCopy
, "getUserMedia:privileged:allow",
2764 nsresult
MediaManager::GenerateUUID(nsAString
& aResult
) {
2766 nsCOMPtr
<nsIUUIDGenerator
> uuidgen
=
2767 do_GetService("@mozilla.org/uuid-generator;1", &rv
);
2768 NS_ENSURE_SUCCESS(rv
, rv
);
2770 // Generate a call ID.
2772 rv
= uuidgen
->GenerateUUIDInPlace(&id
);
2773 NS_ENSURE_SUCCESS(rv
, rv
);
2775 char buffer
[NSID_LENGTH
];
2776 id
.ToProvidedString(buffer
);
2777 aResult
.Assign(NS_ConvertUTF8toUTF16(buffer
));
2781 enum class GetUserMediaSecurityState
{
2792 * This function is used in getUserMedia when privacy.resistFingerprinting is
2793 * true. Only mediaSource of audio/video constraint will be kept. On mobile
2794 * facing mode is also kept.
2796 static void ReduceConstraint(
2797 OwningBooleanOrMediaTrackConstraints
& aConstraint
) {
2798 // Not requesting stream.
2799 if (!MediaManager::IsOn(aConstraint
)) {
2803 // It looks like {audio: true}, do nothing.
2804 if (!aConstraint
.IsMediaTrackConstraints()) {
2808 // Keep mediaSource.
2809 Maybe
<nsString
> mediaSource
;
2810 if (aConstraint
.GetAsMediaTrackConstraints().mMediaSource
.WasPassed()) {
2812 Some(aConstraint
.GetAsMediaTrackConstraints().mMediaSource
.Value());
2815 Maybe
<OwningStringOrStringSequenceOrConstrainDOMStringParameters
> facingMode
;
2816 if (aConstraint
.GetAsMediaTrackConstraints().mFacingMode
.WasPassed()) {
2818 Some(aConstraint
.GetAsMediaTrackConstraints().mFacingMode
.Value());
2821 aConstraint
.Uninit();
2823 aConstraint
.SetAsMediaTrackConstraints().mMediaSource
.Construct(
2826 Unused
<< aConstraint
.SetAsMediaTrackConstraints();
2829 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT)
2831 aConstraint
.SetAsMediaTrackConstraints().mFacingMode
.Construct(*facingMode
);
2833 Unused
<< aConstraint
.SetAsMediaTrackConstraints();
2839 * The entry point for this file. A call from Navigator::mozGetUserMedia
2840 * will end up here. MediaManager is a singleton that is responsible
2841 * for handling all incoming getUserMedia calls from every window.
2843 RefPtr
<MediaManager::StreamPromise
> MediaManager::GetUserMedia(
2844 nsPIDOMWindowInner
* aWindow
,
2845 const MediaStreamConstraints
& aConstraintsPassedIn
,
2846 CallerType aCallerType
) {
2847 MOZ_ASSERT(NS_IsMainThread());
2848 MOZ_ASSERT(aWindow
);
2849 uint64_t windowID
= aWindow
->WindowID();
2851 MediaStreamConstraints
c(aConstraintsPassedIn
); // use a modifiable copy
2853 if (sHasMainThreadShutdown
) {
2854 return StreamPromise::CreateAndReject(
2855 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
,
2860 // Determine permissions early (while we still have a stack).
2862 nsIURI
* docURI
= aWindow
->GetDocumentURI();
2864 return StreamPromise::CreateAndReject(
2865 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
), __func__
);
2867 bool isChrome
= (aCallerType
== CallerType::System
);
2870 Preferences::GetBool("media.navigator.permission.disabled", false);
2871 bool isSecure
= aWindow
->IsSecureContext();
2872 bool isHandlingUserInput
= UserActivation::IsHandlingUserInput();
2874 nsresult rv
= docURI
->GetHost(host
);
2876 nsCOMPtr
<nsIPrincipal
> principal
=
2877 nsGlobalWindowInner::Cast(aWindow
)->GetPrincipal();
2878 if (NS_WARN_IF(!principal
)) {
2879 return StreamPromise::CreateAndReject(
2880 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::SecurityError
),
2884 Document
* doc
= aWindow
->GetExtantDoc();
2885 if (NS_WARN_IF(!doc
)) {
2886 return StreamPromise::CreateAndReject(
2887 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::SecurityError
),
2891 // Disallow access to null principal pages and http pages (unless pref)
2892 if (principal
->GetIsNullPrincipal() ||
2893 !(isSecure
|| StaticPrefs::media_getusermedia_insecure_enabled())) {
2894 return StreamPromise::CreateAndReject(
2895 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
2899 // This principal needs to be sent to different threads and so via IPC.
2900 // For this reason it's better to convert it to PrincipalInfo right now.
2901 ipc::PrincipalInfo principalInfo
;
2902 rv
= PrincipalToPrincipalInfo(principal
, &principalInfo
);
2903 if (NS_WARN_IF(NS_FAILED(rv
))) {
2904 return StreamPromise::CreateAndReject(
2905 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::SecurityError
),
2909 const bool resistFingerprinting
=
2910 !isChrome
&& doc
->ShouldResistFingerprinting(RFPTarget::MediaDevices
);
2911 if (resistFingerprinting
) {
2912 ReduceConstraint(c
.mVideo
);
2913 ReduceConstraint(c
.mAudio
);
2916 if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
2917 c
.mVideo
.SetAsBoolean() = false;
2920 MediaSourceEnum videoType
= MediaSourceEnum::Other
; // none
2921 MediaSourceEnum audioType
= MediaSourceEnum::Other
; // none
2923 if (c
.mVideo
.IsMediaTrackConstraints()) {
2924 auto& vc
= c
.mVideo
.GetAsMediaTrackConstraints();
2925 if (!vc
.mMediaSource
.WasPassed()) {
2926 vc
.mMediaSource
.Construct().AssignASCII(
2927 dom::GetEnumString(MediaSourceEnum::Camera
));
2929 videoType
= dom::StringToEnum
<MediaSourceEnum
>(vc
.mMediaSource
.Value())
2930 .valueOr(MediaSourceEnum::Other
);
2931 Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE
,
2932 (uint32_t)videoType
);
2933 switch (videoType
) {
2934 case MediaSourceEnum::Camera
:
2937 case MediaSourceEnum::Browser
:
2938 // If no window id is passed in then default to the caller's window.
2939 // Functional defaults are helpful in tests, but also a natural outcome
2940 // of the constraints API's limited semantics for requiring input.
2941 if (!vc
.mBrowserWindow
.WasPassed()) {
2942 nsPIDOMWindowOuter
* outer
= aWindow
->GetOuterWindow();
2943 vc
.mBrowserWindow
.Construct(outer
->WindowID());
2946 case MediaSourceEnum::Screen
:
2947 case MediaSourceEnum::Window
:
2948 // Deny screensharing request if support is disabled, or
2949 // the requesting document is not from a host on the whitelist.
2950 if (!Preferences::GetBool(
2951 ((videoType
== MediaSourceEnum::Browser
)
2952 ? "media.getusermedia.browser.enabled"
2953 : "media.getusermedia.screensharing.enabled"),
2955 (!privileged
&& !aWindow
->IsSecureContext())) {
2956 return StreamPromise::CreateAndReject(
2957 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
2962 case MediaSourceEnum::Microphone
:
2963 case MediaSourceEnum::Other
:
2965 return StreamPromise::CreateAndReject(
2966 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::OverconstrainedError
,
2967 "", u
"mediaSource"_ns
),
2973 // Only allow privileged content to explicitly pick full-screen,
2974 // application or tabsharing, since these modes are still available for
2975 // testing. All others get "Window" (*) sharing.
2977 // *) We overload "Window" with the new default getDisplayMedia spec-
2978 // mandated behavior of not influencing user-choice, which we currently
2979 // implement as a list containing BOTH windows AND screen(s).
2981 // Notes on why we chose "Window" as the one to overload. Two reasons:
2983 // 1. It's the closest logically & behaviorally (multi-choice, no default)
2984 // 2. Screen is still useful in tests (implicit default is entire screen)
2986 // For UX reasons we don't want "Entire Screen" to be the first/default
2987 // choice (in our code first=default). It's a "scary" source that comes
2988 // with complicated warnings on-top that would be confusing as the first
2989 // thing people see, and also deserves to be listed as last resort for
2992 if (videoType
== MediaSourceEnum::Screen
||
2993 videoType
== MediaSourceEnum::Browser
) {
2994 videoType
= MediaSourceEnum::Window
;
2995 vc
.mMediaSource
.Value().AssignASCII(dom::GetEnumString(videoType
));
2997 // only allow privileged content to set the window id
2998 if (vc
.mBrowserWindow
.WasPassed()) {
2999 vc
.mBrowserWindow
.Value() = -1;
3001 if (vc
.mAdvanced
.WasPassed()) {
3002 for (MediaTrackConstraintSet
& cs
: vc
.mAdvanced
.Value()) {
3003 if (cs
.mBrowserWindow
.WasPassed()) {
3004 cs
.mBrowserWindow
.Value() = -1;
3009 } else if (IsOn(c
.mVideo
)) {
3010 videoType
= MediaSourceEnum::Camera
;
3011 Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE
,
3012 (uint32_t)videoType
);
3015 if (c
.mAudio
.IsMediaTrackConstraints()) {
3016 auto& ac
= c
.mAudio
.GetAsMediaTrackConstraints();
3017 if (!ac
.mMediaSource
.WasPassed()) {
3018 ac
.mMediaSource
.Construct(NS_ConvertASCIItoUTF16(
3019 dom::GetEnumString(MediaSourceEnum::Microphone
)));
3021 audioType
= dom::StringToEnum
<MediaSourceEnum
>(ac
.mMediaSource
.Value())
3022 .valueOr(MediaSourceEnum::Other
);
3023 Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE
,
3024 (uint32_t)audioType
);
3026 switch (audioType
) {
3027 case MediaSourceEnum::Microphone
:
3030 case MediaSourceEnum::AudioCapture
:
3031 // Only enable AudioCapture if the pref is enabled. If it's not, we can
3033 if (!Preferences::GetBool("media.getusermedia.audio.capture.enabled")) {
3034 return StreamPromise::CreateAndReject(
3035 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
3040 case MediaSourceEnum::Other
:
3042 return StreamPromise::CreateAndReject(
3043 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::OverconstrainedError
,
3044 "", u
"mediaSource"_ns
),
3048 } else if (IsOn(c
.mAudio
)) {
3049 audioType
= MediaSourceEnum::Microphone
;
3050 Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE
,
3051 (uint32_t)audioType
);
3054 // Create a window listener if it doesn't already exist.
3055 RefPtr
<GetUserMediaWindowListener
> windowListener
=
3056 GetOrMakeWindowListener(aWindow
);
3057 MOZ_ASSERT(windowListener
);
3058 // Create an inactive DeviceListener to act as a placeholder, so the
3059 // window listener doesn't clean itself up until we're done.
3060 auto placeholderListener
= MakeRefPtr
<DeviceListener
>();
3061 windowListener
->Register(placeholderListener
);
3063 { // Check Permissions Policy. Reject if a requested feature is disabled.
3064 bool disabled
= !IsOn(c
.mAudio
) && !IsOn(c
.mVideo
);
3065 if (IsOn(c
.mAudio
)) {
3066 if (audioType
== MediaSourceEnum::Microphone
) {
3067 if (Preferences::GetBool("media.getusermedia.microphone.deny", false) ||
3068 !FeaturePolicyUtils::IsFeatureAllowed(doc
, u
"microphone"_ns
)) {
3071 } else if (!FeaturePolicyUtils::IsFeatureAllowed(doc
,
3072 u
"display-capture"_ns
)) {
3076 if (IsOn(c
.mVideo
)) {
3077 if (videoType
== MediaSourceEnum::Camera
) {
3078 if (Preferences::GetBool("media.getusermedia.camera.deny", false) ||
3079 !FeaturePolicyUtils::IsFeatureAllowed(doc
, u
"camera"_ns
)) {
3082 } else if (!FeaturePolicyUtils::IsFeatureAllowed(doc
,
3083 u
"display-capture"_ns
)) {
3089 placeholderListener
->Stop();
3090 return StreamPromise::CreateAndReject(
3091 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
3096 // Get list of all devices, with origin-specific device ids.
3098 MediaEnginePrefs prefs
= mPrefs
;
3101 rv
= GenerateUUID(callID
);
3102 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
));
3104 bool hasVideo
= videoType
!= MediaSourceEnum::Other
;
3105 bool hasAudio
= audioType
!= MediaSourceEnum::Other
;
3107 // Handle fake requests from content. For gUM we don't consider resist
3108 // fingerprinting as users should be prompted anyway.
3109 bool forceFakes
= c
.mFake
.WasPassed() && c
.mFake
.Value();
3110 // fake:true is effective only for microphone and camera devices, so
3111 // permission must be requested for screen capture even if fake:true is set.
3112 bool hasOnlyForcedFakes
=
3113 forceFakes
&& (!hasVideo
|| videoType
== MediaSourceEnum::Camera
) &&
3114 (!hasAudio
|| audioType
== MediaSourceEnum::Microphone
);
3115 bool askPermission
=
3117 Preferences::GetBool("media.navigator.permission.force")) &&
3118 (!hasOnlyForcedFakes
||
3119 Preferences::GetBool("media.navigator.permission.fake"));
3121 LOG("%s: Preparing to enumerate devices. windowId=%" PRIu64
3122 ", videoType=%" PRIu8
", audioType=%" PRIu8
3123 ", forceFakes=%s, askPermission=%s",
3124 __func__
, windowID
, static_cast<uint8_t>(videoType
),
3125 static_cast<uint8_t>(audioType
), forceFakes
? "true" : "false",
3126 askPermission
? "true" : "false");
3128 EnumerationFlags flags
= EnumerationFlag::AllowPermissionRequest
;
3130 flags
+= EnumerationFlag::ForceFakes
;
3132 RefPtr
<MediaManager
> self
= this;
3133 return EnumerateDevicesImpl(
3134 aWindow
, CreateEnumerationParams(videoType
, audioType
, flags
))
3136 GetCurrentSerialEventTarget(), __func__
,
3137 [self
, windowID
, c
, windowListener
,
3138 aCallerType
](RefPtr
<LocalMediaDeviceSetRefCnt
> aDevices
) {
3139 LOG("GetUserMedia: post enumeration promise success callback "
3141 // Ensure that our windowID is still good.
3142 RefPtr
<nsPIDOMWindowInner
> window
=
3143 nsGlobalWindowInner::GetInnerWindowWithId(windowID
);
3144 if (!window
|| !self
->IsWindowListenerStillActive(windowListener
)) {
3145 LOG("GetUserMedia: bad window (%" PRIu64
3146 ") in post enumeration success callback!",
3148 return LocalDeviceSetPromise::CreateAndReject(
3149 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
),
3152 // Apply any constraints. This modifies the passed-in list.
3153 return self
->SelectSettings(c
, aCallerType
, std::move(aDevices
));
3155 [](RefPtr
<MediaMgrError
>&& aError
) {
3156 LOG("GetUserMedia: post enumeration EnumerateDevicesImpl "
3157 "failure callback called!");
3158 return LocalDeviceSetPromise::CreateAndReject(std::move(aError
),
3162 GetCurrentSerialEventTarget(), __func__
,
3163 [self
, windowID
, c
, windowListener
, placeholderListener
, hasAudio
,
3164 hasVideo
, askPermission
, prefs
, isSecure
, isHandlingUserInput
,
3165 callID
, principalInfo
, aCallerType
, resistFingerprinting
,
3166 audioType
](RefPtr
<LocalMediaDeviceSetRefCnt
> aDevices
) mutable {
3167 LOG("GetUserMedia: starting post enumeration promise2 success "
3170 // Ensure that the window is still good.
3171 RefPtr
<nsPIDOMWindowInner
> window
=
3172 nsGlobalWindowInner::GetInnerWindowWithId(windowID
);
3173 if (!window
|| !self
->IsWindowListenerStillActive(windowListener
)) {
3174 LOG("GetUserMedia: bad window (%" PRIu64
3175 ") in post enumeration success callback 2!",
3177 placeholderListener
->Stop();
3178 return StreamPromise::CreateAndReject(
3179 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
),
3182 if (!aDevices
->Length()) {
3183 LOG("GetUserMedia: no devices found in post enumeration promise2 "
3184 "success callback! Calling error handler!");
3185 placeholderListener
->Stop();
3186 // When privacy.resistFingerprinting = true, no
3187 // available device implies content script is requesting
3188 // a fake device, so report NotAllowedError.
3189 auto error
= resistFingerprinting
3190 ? MediaMgrError::Name::NotAllowedError
3191 : MediaMgrError::Name::NotFoundError
;
3192 return StreamPromise::CreateAndReject(
3193 MakeRefPtr
<MediaMgrError
>(error
), __func__
);
3196 // Time to start devices. Create the necessary device listeners and
3197 // remove the placeholder.
3198 RefPtr
<DeviceListener
> audioListener
;
3199 RefPtr
<DeviceListener
> videoListener
;
3201 audioListener
= MakeRefPtr
<DeviceListener
>();
3202 windowListener
->Register(audioListener
);
3205 videoListener
= MakeRefPtr
<DeviceListener
>();
3206 windowListener
->Register(videoListener
);
3208 placeholderListener
->Stop();
3210 bool focusSource
= mozilla::Preferences::GetBool(
3211 "media.getusermedia.window.focus_source.enabled", true);
3213 // Incremental hack to compile. To be replaced by deeper
3214 // refactoring. MediaManager allows
3215 // "neither-resolve-nor-reject" semantics, so we cannot
3216 // use MozPromiseHolder here.
3217 MozPromiseHolder
<StreamPromise
> holder
;
3218 RefPtr
<StreamPromise
> p
= holder
.Ensure(__func__
);
3220 // Pass callbacks and listeners along to GetUserMediaStreamTask.
3221 auto task
= MakeRefPtr
<GetUserMediaStreamTask
>(
3222 c
, std::move(holder
), windowID
, std::move(windowListener
),
3223 std::move(audioListener
), std::move(videoListener
), prefs
,
3224 principalInfo
, aCallerType
, focusSource
);
3226 // It is time to ask for user permission, prime voice processing
3227 // now. Use a local lambda to enable a guard pattern.
3229 if (audioType
!= MediaSourceEnum::Microphone
) {
3234 media_getusermedia_microphone_voice_stream_priming_enabled() ||
3236 media_getusermedia_microphone_prefer_voice_stream_with_processing_enabled()) {
3240 if (const auto fc
= FlattenedConstraints(
3241 NormalizedConstraints(GetInvariant(c
.mAudio
)));
3242 !fc
.mEchoCancellation
.Get(prefs
.mAecOn
) &&
3243 !fc
.mAutoGainControl
.Get(prefs
.mAgcOn
&& prefs
.mAecOn
) &&
3244 !fc
.mNoiseSuppression
.Get(prefs
.mNoiseOn
&& prefs
.mAecOn
)) {
3248 if (GetPersistentPermissions(windowID
)
3249 .map([](auto&& aState
) {
3250 return aState
.mMicrophonePermission
==
3251 PersistentPermissionState::Deny
;
3257 task
->PrimeVoiceProcessing();
3261 self
->AddTaskAndGetCount(windowID
, callID
, std::move(task
));
3263 if (!askPermission
) {
3264 self
->NotifyAllowed(callID
, *aDevices
);
3266 auto req
= MakeRefPtr
<GetUserMediaRequest
>(
3267 window
, callID
, std::move(aDevices
), c
, isSecure
,
3268 isHandlingUserInput
);
3269 if (!Preferences::GetBool("media.navigator.permission.force") &&
3271 // there is at least 1 pending gUM request
3272 // For the scarySources test case, always send the
3274 self
->mPendingGUMRequest
.AppendElement(req
.forget());
3276 nsCOMPtr
<nsIObserverService
> obs
=
3277 services::GetObserverService();
3278 obs
->NotifyObservers(req
, "getUserMedia:request", nullptr);
3282 self
->mLogHandle
= EnsureWebrtcLogging();
3286 [placeholderListener
](RefPtr
<MediaMgrError
>&& aError
) {
3287 LOG("GetUserMedia: post enumeration SelectSettings failure "
3288 "callback called!");
3289 placeholderListener
->Stop();
3290 return StreamPromise::CreateAndReject(std::move(aError
), __func__
);
3294 RefPtr
<LocalDeviceSetPromise
> MediaManager::AnonymizeDevices(
3295 nsPIDOMWindowInner
* aWindow
, RefPtr
<const MediaDeviceSetRefCnt
> aDevices
) {
3296 // Get an origin-key (for either regular or private browsing).
3297 MOZ_ASSERT(NS_IsMainThread());
3298 uint64_t windowId
= aWindow
->WindowID();
3299 nsCOMPtr
<nsIPrincipal
> principal
=
3300 nsGlobalWindowInner::Cast(aWindow
)->GetPrincipal();
3301 MOZ_ASSERT(principal
);
3302 ipc::PrincipalInfo principalInfo
;
3303 nsresult rv
= PrincipalToPrincipalInfo(principal
, &principalInfo
);
3304 if (NS_WARN_IF(NS_FAILED(rv
))) {
3305 return LocalDeviceSetPromise::CreateAndReject(
3306 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
3309 bool resistFingerprinting
=
3310 aWindow
->AsGlobal()->ShouldResistFingerprinting(RFPTarget::MediaDevices
);
3312 IsActivelyCapturingOrHasAPermission(windowId
) && !resistFingerprinting
;
3313 return media::GetPrincipalKey(principalInfo
, persist
)
3315 GetMainThreadSerialEventTarget(), __func__
,
3316 [rawDevices
= std::move(aDevices
), windowId
,
3317 resistFingerprinting
](const nsCString
& aOriginKey
) {
3318 MOZ_ASSERT(!aOriginKey
.IsEmpty());
3319 RefPtr anonymized
= new LocalMediaDeviceSetRefCnt();
3320 for (const RefPtr
<MediaDevice
>& device
: *rawDevices
) {
3321 nsString name
= device
->mRawName
;
3322 if (name
.Find(u
"AirPods"_ns
) != -1) {
3323 name
= u
"AirPods"_ns
;
3326 nsString id
= device
->mRawID
;
3327 if (resistFingerprinting
) {
3328 nsRFPService::GetMediaDeviceName(name
, device
->mKind
);
3330 id
.AppendInt(windowId
);
3332 // An empty id represents a virtual default device, for which
3333 // the exposed deviceId is the empty string.
3334 if (!id
.IsEmpty()) {
3335 nsContentUtils::AnonymizeId(id
, aOriginKey
);
3338 nsString groupId
= device
->mRawGroupID
;
3339 if (resistFingerprinting
) {
3340 nsRFPService::GetMediaDeviceGroup(groupId
, device
->mKind
);
3342 // Use window id to salt group id in order to make it session
3343 // based as required by the spec. This does not provide unique
3344 // group ids through out a browser restart. However, this is not
3345 // against the spec. Furthermore, since device ids are the same
3346 // after a browser restart the fingerprint is not bigger.
3347 groupId
.AppendInt(windowId
);
3348 nsContentUtils::AnonymizeId(groupId
, aOriginKey
);
3349 anonymized
->EmplaceBack(
3350 new LocalMediaDevice(device
, id
, groupId
, name
));
3352 return LocalDeviceSetPromise::CreateAndResolve(anonymized
,
3356 NS_WARNING("AnonymizeDevices failed to get Principal Key");
3357 return LocalDeviceSetPromise::CreateAndReject(
3358 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
),
3363 RefPtr
<LocalDeviceSetPromise
> MediaManager::EnumerateDevicesImpl(
3364 nsPIDOMWindowInner
* aWindow
, EnumerationParams aParams
) {
3365 MOZ_ASSERT(NS_IsMainThread());
3367 uint64_t windowId
= aWindow
->WindowID();
3368 LOG("%s: windowId=%" PRIu64
", aVideoInputType=%" PRIu8
3369 ", aAudioInputType=%" PRIu8
,
3370 __func__
, windowId
, static_cast<uint8_t>(aParams
.VideoInputType()),
3371 static_cast<uint8_t>(aParams
.AudioInputType()));
3373 // To get a device list anonymized for a particular origin, we must:
3374 // 1. Get the raw devices list
3375 // 2. Anonymize the raw list with an origin-key.
3377 // Add the window id here to check for that and abort silently if no longer
3379 RefPtr
<GetUserMediaWindowListener
> windowListener
=
3380 GetOrMakeWindowListener(aWindow
);
3381 MOZ_ASSERT(windowListener
);
3382 // Create an inactive DeviceListener to act as a placeholder, so the
3383 // window listener doesn't clean itself up until we're done.
3384 auto placeholderListener
= MakeRefPtr
<DeviceListener
>();
3385 windowListener
->Register(placeholderListener
);
3387 return MaybeRequestPermissionAndEnumerateRawDevices(std::move(aParams
))
3389 GetMainThreadSerialEventTarget(), __func__
,
3390 [self
= RefPtr(this), this, window
= nsCOMPtr(aWindow
),
3391 placeholderListener
](RefPtr
<MediaDeviceSetRefCnt
> aDevices
) mutable {
3392 // Only run if window is still on our active list.
3393 MediaManager
* mgr
= MediaManager::GetIfExists();
3394 if (!mgr
|| placeholderListener
->Stopped()) {
3395 // The listener has already been removed if the window is no
3397 return LocalDeviceSetPromise::CreateAndReject(
3398 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
),
3401 MOZ_ASSERT(mgr
->IsWindowStillActive(window
->WindowID()));
3402 placeholderListener
->Stop();
3403 return AnonymizeDevices(window
, aDevices
);
3405 [placeholderListener
](RefPtr
<MediaMgrError
>&& aError
) {
3406 // EnumerateDevicesImpl may fail if a new doc has been set, in which
3407 // case the OnNavigation() method should have removed all previous
3408 // active listeners, or if a platform device access request was not
3410 placeholderListener
->Stop();
3411 return LocalDeviceSetPromise::CreateAndReject(std::move(aError
),
3416 RefPtr
<LocalDevicePromise
> MediaManager::SelectAudioOutput(
3417 nsPIDOMWindowInner
* aWindow
, const dom::AudioOutputOptions
& aOptions
,
3418 CallerType aCallerType
) {
3419 bool isHandlingUserInput
= UserActivation::IsHandlingUserInput();
3420 nsCOMPtr
<nsIPrincipal
> principal
=
3421 nsGlobalWindowInner::Cast(aWindow
)->GetPrincipal();
3422 if (!FeaturePolicyUtils::IsFeatureAllowed(aWindow
->GetExtantDoc(),
3423 u
"speaker-selection"_ns
)) {
3424 return LocalDevicePromise::CreateAndReject(
3425 MakeRefPtr
<MediaMgrError
>(
3426 MediaMgrError::Name::NotAllowedError
,
3427 "Document's Permissions Policy does not allow selectAudioOutput()"),
3430 if (NS_WARN_IF(!principal
)) {
3431 return LocalDevicePromise::CreateAndReject(
3432 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::SecurityError
),
3435 // Disallow access to null principal.
3436 if (principal
->GetIsNullPrincipal()) {
3437 return LocalDevicePromise::CreateAndReject(
3438 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
3441 ipc::PrincipalInfo principalInfo
;
3442 nsresult rv
= PrincipalToPrincipalInfo(principal
, &principalInfo
);
3443 if (NS_WARN_IF(NS_FAILED(rv
))) {
3444 return LocalDevicePromise::CreateAndReject(
3445 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::SecurityError
),
3448 uint64_t windowID
= aWindow
->WindowID();
3449 const bool resistFingerprinting
=
3450 aWindow
->AsGlobal()->ShouldResistFingerprinting(aCallerType
,
3451 RFPTarget::MediaDevices
);
3452 return EnumerateDevicesImpl(
3453 aWindow
, CreateEnumerationParams(
3454 MediaSourceEnum::Other
, MediaSourceEnum::Other
,
3455 {EnumerationFlag::EnumerateAudioOutputs
,
3456 EnumerationFlag::AllowPermissionRequest
}))
3458 GetCurrentSerialEventTarget(), __func__
,
3459 [self
= RefPtr
<MediaManager
>(this), windowID
, aOptions
, aCallerType
,
3460 resistFingerprinting
, isHandlingUserInput
,
3461 principalInfo
](RefPtr
<LocalMediaDeviceSetRefCnt
> aDevices
) mutable {
3462 // Ensure that the window is still good.
3463 RefPtr
<nsPIDOMWindowInner
> window
=
3464 nsGlobalWindowInner::GetInnerWindowWithId(windowID
);
3466 LOG("SelectAudioOutput: bad window (%" PRIu64
3467 ") in post enumeration success callback!",
3469 return LocalDevicePromise::CreateAndReject(
3470 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
),
3473 if (aDevices
->IsEmpty()) {
3474 LOG("SelectAudioOutput: no devices found");
3475 auto error
= resistFingerprinting
3476 ? MediaMgrError::Name::NotAllowedError
3477 : MediaMgrError::Name::NotFoundError
;
3478 return LocalDevicePromise::CreateAndReject(
3479 MakeRefPtr
<MediaMgrError
>(error
), __func__
);
3481 MozPromiseHolder
<LocalDevicePromise
> holder
;
3482 RefPtr
<LocalDevicePromise
> p
= holder
.Ensure(__func__
);
3483 auto task
= MakeRefPtr
<SelectAudioOutputTask
>(
3484 std::move(holder
), windowID
, aCallerType
, principalInfo
);
3486 nsresult rv
= GenerateUUID(callID
);
3487 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
));
3489 self
->AddTaskAndGetCount(windowID
, callID
, std::move(task
));
3490 bool askPermission
=
3491 !Preferences::GetBool("media.navigator.permission.disabled") ||
3492 Preferences::GetBool("media.navigator.permission.force");
3493 if (!askPermission
) {
3494 self
->NotifyAllowed(callID
, *aDevices
);
3496 MOZ_ASSERT(window
->IsSecureContext());
3497 auto req
= MakeRefPtr
<GetUserMediaRequest
>(
3498 window
, callID
, std::move(aDevices
), aOptions
, true,
3499 isHandlingUserInput
);
3500 if (taskCount
> 1) {
3501 // there is at least 1 pending gUM request
3502 self
->mPendingGUMRequest
.AppendElement(req
.forget());
3504 nsCOMPtr
<nsIObserverService
> obs
=
3505 services::GetObserverService();
3506 obs
->NotifyObservers(req
, "getUserMedia:request", nullptr);
3511 [](RefPtr
<MediaMgrError
> aError
) {
3512 LOG("SelectAudioOutput: EnumerateDevicesImpl "
3513 "failure callback called!");
3514 return LocalDevicePromise::CreateAndReject(std::move(aError
),
3519 MediaEngine
* MediaManager::GetBackend() {
3520 MOZ_ASSERT(MediaManager::IsInMediaThread());
3521 // Plugin backends as appropriate. The default engine also currently
3522 // includes picture support for Android.
3523 // This IS called off main-thread.
3525 #if defined(MOZ_WEBRTC)
3526 mBackend
= new MediaEngineWebRTC();
3528 mBackend
= new MediaEngineFake();
3530 mDeviceListChangeListener
= mBackend
->DeviceListChangeEvent().Connect(
3531 AbstractThread::MainThread(), this, &MediaManager::DeviceListChanged
);
3536 void MediaManager::OnNavigation(uint64_t aWindowID
) {
3537 MOZ_ASSERT(NS_IsMainThread());
3538 LOG("OnNavigation for %" PRIu64
, aWindowID
);
3540 // Stop the streams for this window. The runnables check this value before
3541 // making a call to content.
3543 nsTArray
<nsString
>* callIDs
;
3544 if (mCallIds
.Get(aWindowID
, &callIDs
)) {
3545 for (auto& callID
: *callIDs
) {
3546 mActiveCallbacks
.Remove(callID
);
3547 for (auto& request
: mPendingGUMRequest
.Clone()) {
3549 request
->GetCallID(id
);
3551 mPendingGUMRequest
.RemoveElement(request
);
3555 mCallIds
.Remove(aWindowID
);
3558 if (RefPtr
<GetUserMediaWindowListener
> listener
=
3559 GetWindowListener(aWindowID
)) {
3560 listener
->RemoveAll();
3562 MOZ_ASSERT(!GetWindowListener(aWindowID
));
3565 void MediaManager::OnCameraMute(bool aMute
) {
3566 MOZ_ASSERT(NS_IsMainThread());
3567 LOG("OnCameraMute for all windows");
3568 mCamerasMuted
= aMute
;
3569 // This is safe since we're on main-thread, and the windowlist can only
3570 // be added to from the main-thread
3571 for (const auto& window
: mActiveWindows
.Values()) {
3572 window
->MuteOrUnmuteCameras(aMute
);
3576 void MediaManager::OnMicrophoneMute(bool aMute
) {
3577 MOZ_ASSERT(NS_IsMainThread());
3578 LOG("OnMicrophoneMute for all windows");
3579 mMicrophonesMuted
= aMute
;
3580 // This is safe since we're on main-thread, and the windowlist can only
3581 // be added to from the main-thread
3582 for (const auto& window
: mActiveWindows
.Values()) {
3583 window
->MuteOrUnmuteMicrophones(aMute
);
3587 RefPtr
<GetUserMediaWindowListener
> MediaManager::GetOrMakeWindowListener(
3588 nsPIDOMWindowInner
* aWindow
) {
3589 Document
* doc
= aWindow
->GetExtantDoc();
3591 // The window has been destroyed. Destroyed windows don't have listeners.
3594 nsIPrincipal
* principal
= doc
->NodePrincipal();
3595 uint64_t windowId
= aWindow
->WindowID();
3596 RefPtr
<GetUserMediaWindowListener
> windowListener
=
3597 GetWindowListener(windowId
);
3598 if (windowListener
) {
3599 MOZ_ASSERT(PrincipalHandleMatches(windowListener
->GetPrincipalHandle(),
3602 windowListener
= new GetUserMediaWindowListener(
3603 windowId
, MakePrincipalHandle(principal
));
3604 AddWindowID(windowId
, windowListener
);
3606 return windowListener
;
3609 void MediaManager::AddWindowID(uint64_t aWindowId
,
3610 RefPtr
<GetUserMediaWindowListener
> aListener
) {
3611 MOZ_ASSERT(NS_IsMainThread());
3612 // Store the WindowID in a hash table and mark as active. The entry is removed
3613 // when this window is closed or navigated away from.
3614 // This is safe since we're on main-thread, and the windowlist can only
3615 // be invalidated from the main-thread (see OnNavigation)
3616 if (IsWindowStillActive(aWindowId
)) {
3617 MOZ_ASSERT(false, "Window already added");
3621 aListener
->MuteOrUnmuteCameras(mCamerasMuted
);
3622 aListener
->MuteOrUnmuteMicrophones(mMicrophonesMuted
);
3623 GetActiveWindows()->InsertOrUpdate(aWindowId
, std::move(aListener
));
3625 RefPtr
<WindowGlobalChild
> wgc
=
3626 WindowGlobalChild::GetByInnerWindowId(aWindowId
);
3628 wgc
->BlockBFCacheFor(BFCacheStatus::ACTIVE_GET_USER_MEDIA
);
3632 void MediaManager::RemoveWindowID(uint64_t aWindowId
) {
3633 RefPtr
<WindowGlobalChild
> wgc
=
3634 WindowGlobalChild::GetByInnerWindowId(aWindowId
);
3636 wgc
->UnblockBFCacheFor(BFCacheStatus::ACTIVE_GET_USER_MEDIA
);
3639 mActiveWindows
.Remove(aWindowId
);
3641 // get outer windowID
3642 auto* window
= nsGlobalWindowInner::GetInnerWindowWithId(aWindowId
);
3644 LOG("No inner window for %" PRIu64
, aWindowId
);
3648 auto* outer
= window
->GetOuterWindow();
3650 LOG("No outer window for inner %" PRIu64
, aWindowId
);
3654 uint64_t outerID
= outer
->WindowID();
3656 // Notify the UI that this window no longer has gUM active
3657 char windowBuffer
[32];
3658 SprintfLiteral(windowBuffer
, "%" PRIu64
, outerID
);
3659 nsString data
= NS_ConvertUTF8toUTF16(windowBuffer
);
3661 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
3662 obs
->NotifyWhenScriptSafe(nullptr, "recording-window-ended", data
.get());
3663 LOG("Sent recording-window-ended for window %" PRIu64
" (outer %" PRIu64
")",
3664 aWindowId
, outerID
);
3667 bool MediaManager::IsWindowListenerStillActive(
3668 const RefPtr
<GetUserMediaWindowListener
>& aListener
) {
3669 MOZ_DIAGNOSTIC_ASSERT(aListener
);
3670 return aListener
&& aListener
== GetWindowListener(aListener
->WindowID());
3673 void MediaManager::GetPref(nsIPrefBranch
* aBranch
, const char* aPref
,
3674 const char* aData
, int32_t* aVal
) {
3676 if (aData
== nullptr || strcmp(aPref
, aData
) == 0) {
3677 if (NS_SUCCEEDED(aBranch
->GetIntPref(aPref
, &temp
))) {
3683 void MediaManager::GetPrefBool(nsIPrefBranch
* aBranch
, const char* aPref
,
3684 const char* aData
, bool* aVal
) {
3686 if (aData
== nullptr || strcmp(aPref
, aData
) == 0) {
3687 if (NS_SUCCEEDED(aBranch
->GetBoolPref(aPref
, &temp
))) {
3693 void MediaManager::GetPrefs(nsIPrefBranch
* aBranch
, const char* aData
) {
3694 GetPref(aBranch
, "media.navigator.video.default_width", aData
,
3696 GetPref(aBranch
, "media.navigator.video.default_height", aData
,
3698 GetPref(aBranch
, "media.navigator.video.default_fps", aData
, &mPrefs
.mFPS
);
3699 GetPref(aBranch
, "media.navigator.audio.fake_frequency", aData
,
3702 GetPrefBool(aBranch
, "media.getusermedia.audio.processing.platform.enabled",
3703 aData
, &mPrefs
.mUsePlatformProcessing
);
3704 GetPrefBool(aBranch
, "media.getusermedia.audio.processing.aec.enabled", aData
,
3706 GetPrefBool(aBranch
, "media.getusermedia.audio.processing.agc.enabled", aData
,
3708 GetPrefBool(aBranch
, "media.getusermedia.audio.processing.hpf.enabled", aData
,
3710 GetPrefBool(aBranch
, "media.getusermedia.audio.processing.noise.enabled",
3711 aData
, &mPrefs
.mNoiseOn
);
3712 GetPrefBool(aBranch
, "media.getusermedia.audio.processing.transient.enabled",
3713 aData
, &mPrefs
.mTransientOn
);
3714 GetPrefBool(aBranch
, "media.getusermedia.audio.processing.agc2.forced", aData
,
3715 &mPrefs
.mAgc2Forced
);
3716 // Use 0 or 1 to force to false or true
3717 // EchoCanceller3Config::echo_removal_control.has_clock_drift.
3718 // -1 is the default, which means automatically set has_clock_drift as
3719 // deemed appropriate.
3720 GetPref(aBranch
, "media.getusermedia.audio.processing.aec.expect_drift",
3721 aData
, &mPrefs
.mExpectDrift
);
3722 GetPref(aBranch
, "media.getusermedia.audio.processing.agc", aData
,
3724 GetPref(aBranch
, "media.getusermedia.audio.processing.noise", aData
,
3726 GetPref(aBranch
, "media.getusermedia.audio.max_channels", aData
,
3729 LOG("%s: default prefs: %dx%d @%dfps, %dHz test tones, platform processing: "
3730 "%s, aec: %s, agc: %s, hpf: %s, noise: %s, drift: %s, agc level: %d, agc "
3732 "%s, noise level: %d, transient: %s, channels %d",
3733 __FUNCTION__
, mPrefs
.mWidth
, mPrefs
.mHeight
, mPrefs
.mFPS
, mPrefs
.mFreq
,
3734 mPrefs
.mUsePlatformProcessing
? "on" : "off",
3735 mPrefs
.mAecOn
? "on" : "off", mPrefs
.mAgcOn
? "on" : "off",
3736 mPrefs
.mHPFOn
? "on" : "off", mPrefs
.mNoiseOn
? "on" : "off",
3737 mPrefs
.mExpectDrift
< 0 ? "auto"
3738 : mPrefs
.mExpectDrift
? "on"
3740 mPrefs
.mAgc
, mPrefs
.mAgc2Forced
? "2" : "1", mPrefs
.mNoise
,
3741 mPrefs
.mTransientOn
? "on" : "off", mPrefs
.mChannels
);
3744 void MediaManager::Shutdown() {
3745 MOZ_ASSERT(NS_IsMainThread());
3746 if (sHasMainThreadShutdown
) {
3750 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
3752 obs
->RemoveObserver(this, "last-pb-context-exited");
3753 obs
->RemoveObserver(this, "getUserMedia:privileged:allow");
3754 obs
->RemoveObserver(this, "getUserMedia:response:allow");
3755 obs
->RemoveObserver(this, "getUserMedia:response:deny");
3756 obs
->RemoveObserver(this, "getUserMedia:response:noOSPermission");
3757 obs
->RemoveObserver(this, "getUserMedia:revoke");
3758 obs
->RemoveObserver(this, "getUserMedia:muteVideo");
3759 obs
->RemoveObserver(this, "getUserMedia:unmuteVideo");
3760 obs
->RemoveObserver(this, "getUserMedia:muteAudio");
3761 obs
->RemoveObserver(this, "getUserMedia:unmuteAudio");
3762 obs
->RemoveObserver(this, "application-background");
3763 obs
->RemoveObserver(this, "application-foreground");
3765 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
3767 ForeachObservedPref([&](const nsLiteralCString
& aPrefName
) {
3768 prefs
->RemoveObserver(aPrefName
, this);
3772 if (mDeviceChangeTimer
) {
3773 mDeviceChangeTimer
->Cancel();
3774 // Drop ref to MediaTimer early to avoid blocking SharedThreadPool shutdown
3775 mDeviceChangeTimer
= nullptr;
3779 // Close off any remaining active windows.
3781 // Live capture at this point is rare but can happen. Stopping it will make
3782 // the window listeners attempt to remove themselves from the active windows
3783 // table. We cannot touch the table at point so we grab a copy of the window
3785 const auto listeners
= ToArray(GetActiveWindows()->Values());
3786 for (const auto& listener
: listeners
) {
3787 listener
->RemoveAll();
3790 MOZ_ASSERT(GetActiveWindows()->Count() == 0);
3792 GetActiveWindows()->Clear();
3793 mActiveCallbacks
.Clear();
3795 mPendingGUMRequest
.Clear();
3797 mLogHandle
= nullptr;
3800 // From main thread's point of view, shutdown is now done.
3801 // All that remains is shutting down the media thread.
3802 sHasMainThreadShutdown
= true;
3804 // Release the backend (and call Shutdown()) from within mMediaThread.
3805 // Don't use MediaManager::Dispatch() because we're
3806 // sHasMainThreadShutdown == true here!
3807 MOZ_ALWAYS_SUCCEEDS(mMediaThread
->Dispatch(
3808 NS_NewRunnableFunction(__func__
, [self
= RefPtr(this), this]() {
3809 LOG("MediaManager Thread Shutdown");
3810 MOZ_ASSERT(IsInMediaThread());
3811 // Must shutdown backend on MediaManager thread, since that's
3812 // where we started it from!
3814 mBackend
->Shutdown(); // idempotent
3815 mDeviceListChangeListener
.DisconnectIfExists();
3817 // last reference, will invoke Shutdown() again
3821 // note that this == sSingleton
3822 MOZ_ASSERT(this == sSingleton
);
3824 // Explicitly shut down the TaskQueue so that it releases its
3825 // SharedThreadPool when all tasks have completed. SharedThreadPool blocks
3826 // XPCOM shutdown from proceeding beyond "xpcom-shutdown-threads" until all
3827 // SharedThreadPools are released, but the nsComponentManager keeps a
3828 // reference to the MediaManager for the nsIMediaManagerService until much
3829 // later in shutdown. This also provides additional assurance that no
3830 // further tasks will be queued.
3831 mMediaThread
->BeginShutdown()->Then(
3832 GetMainThreadSerialEventTarget(), __func__
, [] {
3833 LOG("MediaManager shutdown lambda running, releasing MediaManager "
3835 // Remove async shutdown blocker
3836 media::MustGetShutdownBarrier()->RemoveBlocker(
3837 sSingleton
->mShutdownBlocker
);
3839 sSingleton
= nullptr;
3843 void MediaManager::SendPendingGUMRequest() {
3844 if (mPendingGUMRequest
.Length() > 0) {
3845 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
3846 obs
->NotifyObservers(mPendingGUMRequest
[0], "getUserMedia:request",
3848 mPendingGUMRequest
.RemoveElementAt(0);
3852 bool IsGUMResponseNoAccess(const char* aTopic
,
3853 MediaMgrError::Name
& aErrorName
) {
3854 if (!strcmp(aTopic
, "getUserMedia:response:deny")) {
3855 aErrorName
= MediaMgrError::Name::NotAllowedError
;
3859 if (!strcmp(aTopic
, "getUserMedia:response:noOSPermission")) {
3860 aErrorName
= MediaMgrError::Name::NotFoundError
;
3867 static MediaSourceEnum
ParseScreenColonWindowID(const char16_t
* aData
,
3868 uint64_t* aWindowIDOut
) {
3869 MOZ_ASSERT(aWindowIDOut
);
3870 // may be windowid or screen:windowid
3871 const nsDependentString
data(aData
);
3872 if (Substring(data
, 0, strlen("screen:")).EqualsLiteral("screen:")) {
3874 *aWindowIDOut
= Substring(data
, strlen("screen:")).ToInteger64(&rv
);
3875 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
));
3876 return MediaSourceEnum::Screen
;
3879 *aWindowIDOut
= data
.ToInteger64(&rv
);
3880 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
));
3881 return MediaSourceEnum::Camera
;
3884 nsresult
MediaManager::Observe(nsISupports
* aSubject
, const char* aTopic
,
3885 const char16_t
* aData
) {
3886 MOZ_ASSERT(NS_IsMainThread());
3888 MediaMgrError::Name gumNoAccessError
= MediaMgrError::Name::NotAllowedError
;
3890 if (!strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
)) {
3891 nsCOMPtr
<nsIPrefBranch
> branch(do_QueryInterface(aSubject
));
3893 GetPrefs(branch
, NS_ConvertUTF16toUTF8(aData
).get());
3894 DeviceListChanged();
3896 } else if (!strcmp(aTopic
, "last-pb-context-exited")) {
3897 // Clear memory of private-browsing-specific deviceIds. Fire and forget.
3898 media::SanitizeOriginKeys(0, true);
3900 } else if (!strcmp(aTopic
, "getUserMedia:got-device-permission")) {
3901 MOZ_ASSERT(aSubject
);
3902 nsCOMPtr
<nsIRunnable
> task
= do_QueryInterface(aSubject
);
3903 MediaManager::Dispatch(NewTaskFrom([task
] { task
->Run(); }));
3905 } else if (!strcmp(aTopic
, "getUserMedia:privileged:allow") ||
3906 !strcmp(aTopic
, "getUserMedia:response:allow")) {
3907 nsString
key(aData
);
3908 RefPtr
<GetUserMediaTask
> task
= TakeGetUserMediaTask(key
);
3913 if (sHasMainThreadShutdown
) {
3914 task
->Denied(MediaMgrError::Name::AbortError
, "In shutdown"_ns
);
3917 if (NS_WARN_IF(!aSubject
)) {
3918 return NS_ERROR_FAILURE
; // ignored
3920 // Permission has been granted. aSubject contains the particular device
3921 // or devices selected and approved by the user, if any.
3922 nsCOMPtr
<nsIArray
> array(do_QueryInterface(aSubject
));
3925 array
->GetLength(&len
);
3926 RefPtr
<LocalMediaDevice
> audioInput
;
3927 RefPtr
<LocalMediaDevice
> videoInput
;
3928 RefPtr
<LocalMediaDevice
> audioOutput
;
3929 for (uint32_t i
= 0; i
< len
; i
++) {
3930 nsCOMPtr
<nsIMediaDevice
> device
;
3931 array
->QueryElementAt(i
, NS_GET_IID(nsIMediaDevice
),
3932 getter_AddRefs(device
));
3933 MOZ_ASSERT(device
); // shouldn't be returning anything else...
3938 // Casting here is safe because a LocalMediaDevice is created
3939 // only in Gecko side, JS can only query for an instance.
3940 auto* dev
= static_cast<LocalMediaDevice
*>(device
.get());
3941 switch (dev
->Kind()) {
3942 case MediaDeviceKind::Videoinput
:
3947 case MediaDeviceKind::Audioinput
:
3952 case MediaDeviceKind::Audiooutput
:
3958 MOZ_CRASH("Unexpected device kind");
3962 if (GetUserMediaStreamTask
* streamTask
= task
->AsGetUserMediaStreamTask()) {
3963 bool needVideo
= IsOn(streamTask
->GetConstraints().mVideo
);
3964 bool needAudio
= IsOn(streamTask
->GetConstraints().mAudio
);
3965 MOZ_ASSERT(needVideo
|| needAudio
);
3967 if ((needVideo
&& !videoInput
) || (needAudio
&& !audioInput
)) {
3968 task
->Denied(MediaMgrError::Name::NotAllowedError
);
3971 streamTask
->Allowed(std::move(audioInput
), std::move(videoInput
));
3974 if (SelectAudioOutputTask
* outputTask
= task
->AsSelectAudioOutputTask()) {
3976 task
->Denied(MediaMgrError::Name::NotAllowedError
);
3979 outputTask
->Allowed(std::move(audioOutput
));
3983 NS_WARNING("Unknown task type in getUserMedia");
3984 return NS_ERROR_FAILURE
;
3986 } else if (IsGUMResponseNoAccess(aTopic
, gumNoAccessError
)) {
3987 nsString
key(aData
);
3988 RefPtr
<GetUserMediaTask
> task
= TakeGetUserMediaTask(key
);
3990 task
->Denied(gumNoAccessError
);
3991 SendPendingGUMRequest();
3995 } else if (!strcmp(aTopic
, "getUserMedia:revoke")) {
3997 if (ParseScreenColonWindowID(aData
, &windowID
) == MediaSourceEnum::Screen
) {
3998 LOG("Revoking ScreenCapture access for window %" PRIu64
, windowID
);
3999 StopScreensharing(windowID
);
4001 LOG("Revoking MediaCapture access for window %" PRIu64
, windowID
);
4002 OnNavigation(windowID
);
4005 } else if (!strcmp(aTopic
, "getUserMedia:muteVideo") ||
4006 !strcmp(aTopic
, "getUserMedia:unmuteVideo")) {
4007 OnCameraMute(!strcmp(aTopic
, "getUserMedia:muteVideo"));
4009 } else if (!strcmp(aTopic
, "getUserMedia:muteAudio") ||
4010 !strcmp(aTopic
, "getUserMedia:unmuteAudio")) {
4011 OnMicrophoneMute(!strcmp(aTopic
, "getUserMedia:muteAudio"));
4013 } else if ((!strcmp(aTopic
, "application-background") ||
4014 !strcmp(aTopic
, "application-foreground")) &&
4015 StaticPrefs::media_getusermedia_camera_background_mute_enabled()) {
4016 // On mobile we turn off any cameras (but not mics) while in the background.
4017 // Keeping things simple for now by duplicating test-covered code above.
4019 // NOTE: If a mobile device ever wants to implement "getUserMedia:muteVideo"
4020 // as well, it'd need to update this code to handle & test the combinations.
4021 OnCameraMute(!strcmp(aTopic
, "application-background"));
4028 MediaManager::CollectReports(nsIHandleReportCallback
* aHandleReport
,
4029 nsISupports
* aData
, bool aAnonymize
) {
4031 amount
+= mActiveWindows
.ShallowSizeOfExcludingThis(MallocSizeOf
);
4032 for (const GetUserMediaWindowListener
* listener
: mActiveWindows
.Values()) {
4033 amount
+= listener
->SizeOfIncludingThis(MallocSizeOf
);
4035 amount
+= mActiveCallbacks
.ShallowSizeOfExcludingThis(MallocSizeOf
);
4036 for (const GetUserMediaTask
* task
: mActiveCallbacks
.Values()) {
4037 // Assume nsString buffers for keys are accounted in mCallIds.
4038 amount
+= task
->SizeOfIncludingThis(MallocSizeOf
);
4040 amount
+= mCallIds
.ShallowSizeOfExcludingThis(MallocSizeOf
);
4041 for (const auto& array
: mCallIds
.Values()) {
4042 amount
+= array
->ShallowSizeOfExcludingThis(MallocSizeOf
);
4043 for (const nsString
& callID
: *array
) {
4044 amount
+= callID
.SizeOfExcludingThisEvenIfShared(MallocSizeOf
);
4047 amount
+= mPendingGUMRequest
.ShallowSizeOfExcludingThis(MallocSizeOf
);
4048 // GetUserMediaRequest pointees of mPendingGUMRequest do not have support
4049 // for memory accounting. mPendingGUMRequest logic should probably be moved
4050 // to the front end (bug 1691625).
4051 MOZ_COLLECT_REPORT("explicit/media/media-manager-aggregates", KIND_HEAP
,
4052 UNITS_BYTES
, amount
,
4053 "Memory used by MediaManager variable length members.");
4057 nsresult
MediaManager::GetActiveMediaCaptureWindows(nsIArray
** aArray
) {
4060 nsCOMPtr
<nsIMutableArray
> array
= nsArray::Create();
4062 for (const auto& entry
: mActiveWindows
) {
4063 const uint64_t& id
= entry
.GetKey();
4064 RefPtr
<GetUserMediaWindowListener
> winListener
= entry
.GetData();
4069 auto* window
= nsGlobalWindowInner::GetInnerWindowWithId(id
);
4076 if (winListener
->CapturingVideo() || winListener
->CapturingAudio()) {
4077 array
->AppendElement(ToSupports(window
));
4081 array
.forget(aArray
);
4085 struct CaptureWindowStateData
{
4087 uint16_t* mMicrophone
;
4088 uint16_t* mScreenShare
;
4089 uint16_t* mWindowShare
;
4090 uint16_t* mAppShare
;
4091 uint16_t* mBrowserShare
;
4095 MediaManager::MediaCaptureWindowState(
4096 nsIDOMWindow
* aCapturedWindow
, uint16_t* aCamera
, uint16_t* aMicrophone
,
4097 uint16_t* aScreen
, uint16_t* aWindow
, uint16_t* aBrowser
,
4098 nsTArray
<RefPtr
<nsIMediaDevice
>>& aDevices
) {
4099 MOZ_ASSERT(NS_IsMainThread());
4101 CaptureState camera
= CaptureState::Off
;
4102 CaptureState microphone
= CaptureState::Off
;
4103 CaptureState screen
= CaptureState::Off
;
4104 CaptureState window
= CaptureState::Off
;
4105 CaptureState browser
= CaptureState::Off
;
4106 RefPtr
<LocalMediaDeviceSetRefCnt
> devices
;
4108 nsCOMPtr
<nsPIDOMWindowInner
> piWin
= do_QueryInterface(aCapturedWindow
);
4110 if (RefPtr
<GetUserMediaWindowListener
> listener
=
4111 GetWindowListener(piWin
->WindowID())) {
4112 camera
= listener
->CapturingSource(MediaSourceEnum::Camera
);
4113 microphone
= listener
->CapturingSource(MediaSourceEnum::Microphone
);
4114 screen
= listener
->CapturingSource(MediaSourceEnum::Screen
);
4115 window
= listener
->CapturingSource(MediaSourceEnum::Window
);
4116 browser
= listener
->CapturingSource(MediaSourceEnum::Browser
);
4117 devices
= listener
->GetDevices();
4121 *aCamera
= FromCaptureState(camera
);
4122 *aMicrophone
= FromCaptureState(microphone
);
4123 *aScreen
= FromCaptureState(screen
);
4124 *aWindow
= FromCaptureState(window
);
4125 *aBrowser
= FromCaptureState(browser
);
4127 for (auto& device
: *devices
) {
4128 aDevices
.AppendElement(device
);
4132 LOG("%s: window %" PRIu64
" capturing %s %s %s %s %s", __FUNCTION__
,
4133 piWin
? piWin
->WindowID() : -1,
4134 *aCamera
== nsIMediaManagerService::STATE_CAPTURE_ENABLED
4135 ? "camera (enabled)"
4136 : (*aCamera
== nsIMediaManagerService::STATE_CAPTURE_DISABLED
4137 ? "camera (disabled)"
4139 *aMicrophone
== nsIMediaManagerService::STATE_CAPTURE_ENABLED
4140 ? "microphone (enabled)"
4141 : (*aMicrophone
== nsIMediaManagerService::STATE_CAPTURE_DISABLED
4142 ? "microphone (disabled)"
4144 *aScreen
? "screenshare" : "", *aWindow
? "windowshare" : "",
4145 *aBrowser
? "browsershare" : "");
4151 MediaManager::SanitizeDeviceIds(int64_t aSinceWhen
) {
4152 MOZ_ASSERT(NS_IsMainThread());
4153 LOG("%s: sinceWhen = %" PRId64
, __FUNCTION__
, aSinceWhen
);
4155 media::SanitizeOriginKeys(aSinceWhen
, false); // we fire and forget
4159 void MediaManager::StopScreensharing(uint64_t aWindowID
) {
4160 // We need to stop window/screensharing for all streams in this innerwindow.
4162 if (RefPtr
<GetUserMediaWindowListener
> listener
=
4163 GetWindowListener(aWindowID
)) {
4164 listener
->StopSharing();
4168 bool MediaManager::IsActivelyCapturingOrHasAPermission(uint64_t aWindowId
) {
4169 // Does page currently have a gUM stream active?
4171 nsCOMPtr
<nsIArray
> array
;
4172 GetActiveMediaCaptureWindows(getter_AddRefs(array
));
4174 array
->GetLength(&len
);
4175 for (uint32_t i
= 0; i
< len
; i
++) {
4176 nsCOMPtr
<nsPIDOMWindowInner
> win
;
4177 array
->QueryElementAt(i
, NS_GET_IID(nsPIDOMWindowInner
),
4178 getter_AddRefs(win
));
4179 if (win
&& win
->WindowID() == aWindowId
) {
4184 // Or are persistent permissions (audio or video) granted?
4186 return GetPersistentPermissions(aWindowId
)
4187 .map([](auto&& aState
) {
4188 return aState
.mMicrophonePermission
==
4189 PersistentPermissionState::Allow
||
4190 aState
.mCameraPermission
== PersistentPermissionState::Allow
;
4195 DeviceListener::DeviceListener()
4197 mMainThreadCheck(nullptr),
4198 mPrincipalHandle(PRINCIPAL_HANDLE_NONE
),
4199 mWindowListener(nullptr) {}
4201 void DeviceListener::Register(GetUserMediaWindowListener
* aListener
) {
4202 LOG("DeviceListener %p registering with window listener %p", this, aListener
);
4204 MOZ_ASSERT(aListener
, "No listener");
4205 MOZ_ASSERT(!mWindowListener
, "Already registered");
4206 MOZ_ASSERT(!Activated(), "Already activated");
4208 mPrincipalHandle
= aListener
->GetPrincipalHandle();
4209 mWindowListener
= aListener
;
4212 void DeviceListener::Activate(RefPtr
<LocalMediaDevice
> aDevice
,
4213 RefPtr
<LocalTrackSource
> aTrackSource
,
4215 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4217 LOG("DeviceListener %p activating %s device %p", this,
4218 dom::GetEnumString(aDevice
->Kind()).get(), aDevice
.get());
4220 MOZ_ASSERT(!mStopped
, "Cannot activate stopped device listener");
4221 MOZ_ASSERT(!Activated(), "Already activated");
4223 mMainThreadCheck
= PR_GetCurrentThread();
4224 bool offWhileDisabled
=
4225 (aDevice
->GetMediaSource() == MediaSourceEnum::Microphone
&&
4226 Preferences::GetBool(
4227 "media.getusermedia.microphone.off_while_disabled.enabled", true)) ||
4228 (aDevice
->GetMediaSource() == MediaSourceEnum::Camera
&&
4229 Preferences::GetBool(
4230 "media.getusermedia.camera.off_while_disabled.enabled", true));
4232 if (MediaEventSource
<void>* event
= aDevice
->Source()->CaptureEndedEvent()) {
4233 mCaptureEndedListener
= event
->Connect(AbstractThread::MainThread(), this,
4234 &DeviceListener::Stop
);
4237 mDeviceState
= MakeUnique
<DeviceState
>(
4238 std::move(aDevice
), std::move(aTrackSource
), offWhileDisabled
);
4239 mDeviceState
->mDeviceMuted
= aStartMuted
;
4241 mDeviceState
->mTrackSource
->Mute();
4245 RefPtr
<DeviceListener::DeviceListenerPromise
>
4246 DeviceListener::InitializeAsync() {
4247 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4248 MOZ_DIAGNOSTIC_ASSERT(!mStopped
);
4250 return MediaManager::Dispatch
<DeviceListenerPromise
>(
4252 [principal
= GetPrincipalHandle(), device
= mDeviceState
->mDevice
,
4253 track
= mDeviceState
->mTrackSource
->mTrack
,
4254 deviceMuted
= mDeviceState
->mDeviceMuted
](
4255 MozPromiseHolder
<DeviceListenerPromise
>& aHolder
) {
4256 auto kind
= device
->Kind();
4257 device
->SetTrack(track
, principal
);
4258 nsresult rv
= deviceMuted
? NS_OK
: device
->Start();
4259 if (kind
== MediaDeviceKind::Audioinput
||
4260 kind
== MediaDeviceKind::Videoinput
) {
4261 if ((rv
== NS_ERROR_NOT_AVAILABLE
&&
4262 kind
== MediaDeviceKind::Audioinput
) ||
4263 (NS_FAILED(rv
) && kind
== MediaDeviceKind::Videoinput
)) {
4265 rv
= device
->Start();
4267 if (rv
== NS_ERROR_NOT_AVAILABLE
&&
4268 kind
== MediaDeviceKind::Audioinput
) {
4270 log
.AssignLiteral("Concurrent mic process limit.");
4271 aHolder
.Reject(MakeRefPtr
<MediaMgrError
>(
4272 MediaMgrError::Name::NotReadableError
,
4278 if (NS_FAILED(rv
)) {
4280 log
.AppendPrintf("Starting %s failed",
4281 dom::GetEnumString(kind
).get());
4283 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
,
4288 LOG("started %s device %p", dom::GetEnumString(kind
).get(),
4290 aHolder
.Resolve(true, __func__
);
4293 GetMainThreadSerialEventTarget(), __func__
,
4294 [self
= RefPtr
<DeviceListener
>(this), this]() {
4296 // We were shut down during the async init
4297 return DeviceListenerPromise::CreateAndResolve(true, __func__
);
4300 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mTrackEnabled
);
4301 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mDeviceEnabled
);
4302 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mStopped
);
4304 mDeviceState
->mDeviceEnabled
= true;
4305 mDeviceState
->mTrackEnabled
= true;
4306 mDeviceState
->mTrackEnabledTime
= TimeStamp::Now();
4307 return DeviceListenerPromise::CreateAndResolve(true, __func__
);
4309 [self
= RefPtr
<DeviceListener
>(this),
4310 this](RefPtr
<MediaMgrError
>&& aResult
) {
4312 return DeviceListenerPromise::CreateAndReject(std::move(aResult
),
4316 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mTrackEnabled
);
4317 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mDeviceEnabled
);
4318 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mStopped
);
4321 return DeviceListenerPromise::CreateAndReject(std::move(aResult
),
4326 void DeviceListener::Stop() {
4327 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4334 LOG("DeviceListener %p stopping", this);
4337 mDeviceState
->mDisableTimer
->Cancel();
4339 if (mDeviceState
->mStopped
) {
4340 // device already stopped.
4343 mDeviceState
->mStopped
= true;
4345 mDeviceState
->mTrackSource
->Stop();
4347 MediaManager::Dispatch(NewTaskFrom([device
= mDeviceState
->mDevice
]() {
4349 device
->Deallocate();
4352 mWindowListener
->ChromeAffectingStateChanged();
4355 mCaptureEndedListener
.DisconnectIfExists();
4357 // Keep a strong ref to the removed window listener.
4358 RefPtr
<GetUserMediaWindowListener
> windowListener
= mWindowListener
;
4359 mWindowListener
= nullptr;
4360 windowListener
->Remove(this);
4363 void DeviceListener::GetSettings(MediaTrackSettings
& aOutSettings
) const {
4364 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4365 LocalMediaDevice
* device
= GetDevice();
4366 device
->GetSettings(aOutSettings
);
4368 MediaSourceEnum mediaSource
= device
->GetMediaSource();
4369 if (mediaSource
== MediaSourceEnum::Camera
||
4370 mediaSource
== MediaSourceEnum::Microphone
) {
4371 aOutSettings
.mDeviceId
.Construct(device
->mID
);
4372 aOutSettings
.mGroupId
.Construct(device
->mGroupID
);
4376 void DeviceListener::GetCapabilities(
4377 MediaTrackCapabilities
& aOutCapabilities
) const {
4378 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4379 LocalMediaDevice
* device
= GetDevice();
4380 device
->GetCapabilities(aOutCapabilities
);
4382 MediaSourceEnum mediaSource
= device
->GetMediaSource();
4383 if (mediaSource
== MediaSourceEnum::Camera
||
4384 mediaSource
== MediaSourceEnum::Microphone
) {
4385 aOutCapabilities
.mDeviceId
.Construct(device
->mID
);
4386 aOutCapabilities
.mGroupId
.Construct(device
->mGroupID
);
4390 auto DeviceListener::UpdateDevice(bool aOn
) -> RefPtr
<DeviceOperationPromise
> {
4391 MOZ_ASSERT(NS_IsMainThread());
4392 RefPtr
<DeviceListener
> self
= this;
4393 DeviceState
& state
= *mDeviceState
;
4394 return MediaManager::Dispatch
<DeviceOperationPromise
>(
4396 [self
, device
= state
.mDevice
,
4397 aOn
](MozPromiseHolder
<DeviceOperationPromise
>& h
) {
4398 LOG("Turning %s device (%s)", aOn
? "on" : "off",
4399 NS_ConvertUTF16toUTF8(device
->mName
).get());
4400 h
.Resolve(aOn
? device
->Start() : device
->Stop(), __func__
);
4403 GetMainThreadSerialEventTarget(), __func__
,
4404 [self
, this, &state
, aOn
](nsresult aResult
) {
4405 if (state
.mStopped
) {
4406 // Device was stopped on main thread during the operation. Done.
4407 return DeviceOperationPromise::CreateAndResolve(aResult
,
4410 LOG("DeviceListener %p turning %s %s input device %s", this,
4412 dom::GetEnumString(GetDevice()->Kind()).get(),
4413 NS_SUCCEEDED(aResult
) ? "succeeded" : "failed");
4415 if (NS_FAILED(aResult
) && aResult
!= NS_ERROR_ABORT
) {
4416 // This path handles errors from starting or stopping the
4417 // device. NS_ERROR_ABORT are for cases where *we* aborted. They
4418 // need graceful handling.
4420 // Starting the device failed. Stopping the track here will
4421 // make the MediaStreamTrack end after a pass through the
4425 // Stopping the device failed. This is odd, but not fatal.
4426 MOZ_ASSERT_UNREACHABLE("The device should be stoppable");
4429 return DeviceOperationPromise::CreateAndResolve(aResult
, __func__
);
4432 MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject");
4433 return DeviceOperationPromise::CreateAndReject(false, __func__
);
4437 void DeviceListener::SetDeviceEnabled(bool aEnable
) {
4438 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4439 MOZ_ASSERT(Activated(), "No device to set enabled state for");
4441 DeviceState
& state
= *mDeviceState
;
4443 LOG("DeviceListener %p %s %s device", this,
4444 aEnable
? "enabling" : "disabling",
4445 dom::GetEnumString(GetDevice()->Kind()).get());
4447 state
.mTrackEnabled
= aEnable
;
4449 if (state
.mStopped
) {
4450 // Device terminally stopped. Updating device state is pointless.
4454 if (state
.mOperationInProgress
) {
4455 // If a timer is in progress, it needs to be canceled now so the next
4456 // DisableTrack() gets a fresh start. Canceling will trigger another
4458 state
.mDisableTimer
->Cancel();
4462 if (state
.mDeviceEnabled
== aEnable
) {
4463 // Device is already in the desired state.
4467 // All paths from here on must end in setting
4468 // `state.mOperationInProgress` to false.
4469 state
.mOperationInProgress
= true;
4471 RefPtr
<MediaTimerPromise
> timerPromise
;
4473 timerPromise
= MediaTimerPromise::CreateAndResolve(true, __func__
);
4474 state
.mTrackEnabledTime
= TimeStamp::Now();
4476 const TimeDuration maxDelay
=
4477 TimeDuration::FromMilliseconds(Preferences::GetUint(
4478 GetDevice()->Kind() == MediaDeviceKind::Audioinput
4479 ? "media.getusermedia.microphone.off_while_disabled.delay_ms"
4480 : "media.getusermedia.camera.off_while_disabled.delay_ms",
4482 const TimeDuration durationEnabled
=
4483 TimeStamp::Now() - state
.mTrackEnabledTime
;
4484 const TimeDuration delay
= TimeDuration::Max(
4485 TimeDuration::FromMilliseconds(0), maxDelay
- durationEnabled
);
4486 timerPromise
= state
.mDisableTimer
->WaitFor(delay
, __func__
);
4489 RefPtr
<DeviceListener
> self
= this;
4492 GetMainThreadSerialEventTarget(), __func__
,
4493 [self
, this, &state
, aEnable
]() mutable {
4494 MOZ_ASSERT(state
.mDeviceEnabled
!= aEnable
,
4495 "Device operation hasn't started");
4496 MOZ_ASSERT(state
.mOperationInProgress
,
4497 "It's our responsibility to reset the inProgress state");
4499 LOG("DeviceListener %p %s %s device - starting device operation",
4500 this, aEnable
? "enabling" : "disabling",
4501 dom::GetEnumString(GetDevice()->Kind()).get());
4503 if (state
.mStopped
) {
4504 // Source was stopped between timer resolving and this runnable.
4505 return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT
,
4509 state
.mDeviceEnabled
= aEnable
;
4511 if (mWindowListener
) {
4512 mWindowListener
->ChromeAffectingStateChanged();
4514 if (!state
.mOffWhileDisabled
|| state
.mDeviceMuted
) {
4515 // If the feature to turn a device off while disabled is itself
4516 // disabled, or the device is currently user agent muted, then
4517 // we shortcut the device operation and tell the
4518 // ux-updating code that everything went fine.
4519 return DeviceOperationPromise::CreateAndResolve(NS_OK
, __func__
);
4521 return UpdateDevice(aEnable
);
4524 // Timer was canceled by us. We signal this with NS_ERROR_ABORT.
4525 return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT
,
4529 GetMainThreadSerialEventTarget(), __func__
,
4530 [self
, this, &state
, aEnable
](nsresult aResult
) mutable {
4531 MOZ_ASSERT_IF(aResult
!= NS_ERROR_ABORT
,
4532 state
.mDeviceEnabled
== aEnable
);
4533 MOZ_ASSERT(state
.mOperationInProgress
);
4534 state
.mOperationInProgress
= false;
4536 if (state
.mStopped
) {
4537 // Device was stopped on main thread during the operation.
4542 if (NS_FAILED(aResult
) && aResult
!= NS_ERROR_ABORT
&& !aEnable
) {
4543 // To keep our internal state sane in this case, we disallow
4544 // future stops due to disable.
4545 state
.mOffWhileDisabled
= false;
4549 // This path is for a device operation aResult that was success or
4550 // NS_ERROR_ABORT (*we* canceled the operation).
4551 // At this point we have to follow up on the intended state, i.e.,
4552 // update the device state if the track state changed in the
4555 if (state
.mTrackEnabled
!= state
.mDeviceEnabled
) {
4556 // Track state changed during this operation. We'll start over.
4557 SetDeviceEnabled(state
.mTrackEnabled
);
4560 []() { MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject"); });
4563 void DeviceListener::SetDeviceMuted(bool aMute
) {
4564 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4565 MOZ_ASSERT(Activated(), "No device to set muted state for");
4567 DeviceState
& state
= *mDeviceState
;
4569 LOG("DeviceListener %p %s %s device", this, aMute
? "muting" : "unmuting",
4570 dom::GetEnumString(GetDevice()->Kind()).get());
4572 if (state
.mStopped
) {
4573 // Device terminally stopped. Updating device state is pointless.
4577 if (state
.mDeviceMuted
== aMute
) {
4578 // Device is already in the desired state.
4582 LOG("DeviceListener %p %s %s device - starting device operation", this,
4583 aMute
? "muting" : "unmuting",
4584 dom::GetEnumString(GetDevice()->Kind()).get());
4586 state
.mDeviceMuted
= aMute
;
4588 if (mWindowListener
) {
4589 mWindowListener
->ChromeAffectingStateChanged();
4591 // Update trackSource to fire mute/unmute events on all its tracks
4593 state
.mTrackSource
->Mute();
4595 state
.mTrackSource
->Unmute();
4597 if (!state
.mOffWhileDisabled
|| !state
.mDeviceEnabled
) {
4598 // If the pref to turn the underlying device is itself off, or the device
4599 // is already off, it's unecessary to do anything else.
4602 UpdateDevice(!aMute
);
4605 void DeviceListener::MuteOrUnmuteCamera(bool aMute
) {
4606 MOZ_ASSERT(NS_IsMainThread());
4612 MOZ_RELEASE_ASSERT(mWindowListener
);
4613 LOG("DeviceListener %p MuteOrUnmuteCamera: %s", this,
4614 aMute
? "mute" : "unmute");
4617 (GetDevice()->GetMediaSource() == MediaSourceEnum::Camera
)) {
4618 SetDeviceMuted(aMute
);
4622 void DeviceListener::MuteOrUnmuteMicrophone(bool aMute
) {
4623 MOZ_ASSERT(NS_IsMainThread());
4629 MOZ_RELEASE_ASSERT(mWindowListener
);
4630 LOG("DeviceListener %p MuteOrUnmuteMicrophone: %s", this,
4631 aMute
? "mute" : "unmute");
4634 (GetDevice()->GetMediaSource() == MediaSourceEnum::Microphone
)) {
4635 SetDeviceMuted(aMute
);
4639 bool DeviceListener::CapturingVideo() const {
4640 MOZ_ASSERT(NS_IsMainThread());
4641 return Activated() && mDeviceState
&& !mDeviceState
->mStopped
&&
4642 MediaEngineSource::IsVideo(GetDevice()->GetMediaSource()) &&
4643 (!GetDevice()->IsFake() ||
4644 Preferences::GetBool("media.navigator.permission.fake"));
4647 bool DeviceListener::CapturingAudio() const {
4648 MOZ_ASSERT(NS_IsMainThread());
4649 return Activated() && mDeviceState
&& !mDeviceState
->mStopped
&&
4650 MediaEngineSource::IsAudio(GetDevice()->GetMediaSource()) &&
4651 (!GetDevice()->IsFake() ||
4652 Preferences::GetBool("media.navigator.permission.fake"));
4655 CaptureState
DeviceListener::CapturingSource(MediaSourceEnum aSource
) const {
4656 MOZ_ASSERT(NS_IsMainThread());
4657 if (GetDevice()->GetMediaSource() != aSource
) {
4658 // This DeviceListener doesn't capture a matching source
4659 return CaptureState::Off
;
4662 if (mDeviceState
->mStopped
) {
4663 // The source is a match but has been permanently stopped
4664 return CaptureState::Off
;
4667 if ((aSource
== MediaSourceEnum::Camera
||
4668 aSource
== MediaSourceEnum::Microphone
) &&
4669 GetDevice()->IsFake() &&
4670 !Preferences::GetBool("media.navigator.permission.fake")) {
4671 // Fake Camera and Microphone only count if there is no fake permission
4672 return CaptureState::Off
;
4675 // Source is a match and is active and unmuted
4677 if (mDeviceState
->mDeviceEnabled
&& !mDeviceState
->mDeviceMuted
) {
4678 return CaptureState::Enabled
;
4681 return CaptureState::Disabled
;
4684 RefPtr
<DeviceListener::DeviceListenerPromise
> DeviceListener::ApplyConstraints(
4685 const MediaTrackConstraints
& aConstraints
, CallerType aCallerType
) {
4686 MOZ_ASSERT(NS_IsMainThread());
4688 if (mStopped
|| mDeviceState
->mStopped
) {
4689 LOG("DeviceListener %p %s device applyConstraints, but device is stopped",
4690 this, dom::GetEnumString(GetDevice()->Kind()).get());
4691 return DeviceListenerPromise::CreateAndResolve(false, __func__
);
4694 MediaManager
* mgr
= MediaManager::GetIfExists();
4696 return DeviceListenerPromise::CreateAndResolve(false, __func__
);
4699 return MediaManager::Dispatch
<DeviceListenerPromise
>(
4700 __func__
, [device
= mDeviceState
->mDevice
, aConstraints
, aCallerType
](
4701 MozPromiseHolder
<DeviceListenerPromise
>& aHolder
) mutable {
4702 MOZ_ASSERT(MediaManager::IsInMediaThread());
4703 MediaManager
* mgr
= MediaManager::GetIfExists();
4704 MOZ_RELEASE_ASSERT(mgr
); // Must exist while media thread is alive
4705 const char* badConstraint
= nullptr;
4707 device
->Reconfigure(aConstraints
, mgr
->mPrefs
, &badConstraint
);
4708 if (NS_FAILED(rv
)) {
4709 if (rv
== NS_ERROR_INVALID_ARG
) {
4710 // Reconfigure failed due to constraints
4711 if (!badConstraint
) {
4712 nsTArray
<RefPtr
<LocalMediaDevice
>> devices
;
4713 devices
.AppendElement(device
);
4714 badConstraint
= MediaConstraintsHelper::SelectSettings(
4715 NormalizedConstraints(aConstraints
), devices
, aCallerType
);
4718 // Unexpected. ApplyConstraints* cannot fail with any other error.
4720 LOG("ApplyConstraints-Task: Unexpected fail %" PRIx32
,
4721 static_cast<uint32_t>(rv
));
4724 aHolder
.Reject(MakeRefPtr
<MediaMgrError
>(
4725 MediaMgrError::Name::OverconstrainedError
, "",
4726 NS_ConvertASCIItoUTF16(badConstraint
)),
4730 // Reconfigure was successful
4731 aHolder
.Resolve(false, __func__
);
4735 PrincipalHandle
DeviceListener::GetPrincipalHandle() const {
4736 return mPrincipalHandle
;
4739 void GetUserMediaWindowListener::StopSharing() {
4740 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4742 for (auto& l
: mActiveListeners
.Clone()) {
4743 MediaSourceEnum source
= l
->GetDevice()->GetMediaSource();
4744 if (source
== MediaSourceEnum::Screen
||
4745 source
== MediaSourceEnum::Window
||
4746 source
== MediaSourceEnum::AudioCapture
) {
4752 void GetUserMediaWindowListener::StopRawID(const nsString
& removedDeviceID
) {
4753 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4755 for (auto& l
: mActiveListeners
.Clone()) {
4756 if (removedDeviceID
.Equals(l
->GetDevice()->RawID())) {
4762 void GetUserMediaWindowListener::MuteOrUnmuteCameras(bool aMute
) {
4763 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4765 if (mCamerasAreMuted
== aMute
) {
4768 mCamerasAreMuted
= aMute
;
4770 for (auto& l
: mActiveListeners
) {
4771 if (l
->GetDevice()->Kind() == MediaDeviceKind::Videoinput
) {
4772 l
->MuteOrUnmuteCamera(aMute
);
4777 void GetUserMediaWindowListener::MuteOrUnmuteMicrophones(bool aMute
) {
4778 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4780 if (mMicrophonesAreMuted
== aMute
) {
4783 mMicrophonesAreMuted
= aMute
;
4785 for (auto& l
: mActiveListeners
) {
4786 if (l
->GetDevice()->Kind() == MediaDeviceKind::Audioinput
) {
4787 l
->MuteOrUnmuteMicrophone(aMute
);
4792 void GetUserMediaWindowListener::ChromeAffectingStateChanged() {
4793 MOZ_ASSERT(NS_IsMainThread());
4795 // We wait until stable state before notifying chrome so chrome only does
4796 // one update if more updates happen in this event loop.
4798 if (mChromeNotificationTaskPosted
) {
4802 nsCOMPtr
<nsIRunnable
> runnable
=
4803 NewRunnableMethod("GetUserMediaWindowListener::NotifyChrome", this,
4804 &GetUserMediaWindowListener::NotifyChrome
);
4805 nsContentUtils::RunInStableState(runnable
.forget());
4806 mChromeNotificationTaskPosted
= true;
4809 void GetUserMediaWindowListener::NotifyChrome() {
4810 MOZ_ASSERT(mChromeNotificationTaskPosted
);
4811 mChromeNotificationTaskPosted
= false;
4813 NS_DispatchToMainThread(NS_NewRunnableFunction(
4814 "MediaManager::NotifyChrome", [windowID
= mWindowID
]() {
4815 auto* window
= nsGlobalWindowInner::GetInnerWindowWithId(windowID
);
4817 MOZ_ASSERT_UNREACHABLE("Should have window");
4821 nsresult rv
= MediaManager::NotifyRecordingStatusChange(window
);
4822 if (NS_FAILED(rv
)) {
4823 MOZ_ASSERT_UNREACHABLE("Should be able to notify chrome");
4831 } // namespace mozilla