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 "MediaTimer.h"
14 #include "MediaTrackConstraints.h"
15 #include "MediaTrackGraph.h"
16 #include "MediaTrackListener.h"
17 #include "VideoStreamTrack.h"
18 #include "VideoUtils.h"
19 #include "mozilla/Base64.h"
20 #include "mozilla/MozPromise.h"
21 #include "mozilla/NullPrincipal.h"
22 #include "mozilla/PeerIdentity.h"
23 #include "mozilla/PermissionDelegateHandler.h"
24 #include "mozilla/Sprintf.h"
25 #include "mozilla/StaticPrefs_media.h"
26 #include "mozilla/Telemetry.h"
27 #include "mozilla/Types.h"
28 #include "mozilla/dom/BindingDeclarations.h"
29 #include "mozilla/dom/Document.h"
30 #include "mozilla/dom/Element.h"
31 #include "mozilla/dom/FeaturePolicyUtils.h"
32 #include "mozilla/dom/File.h"
33 #include "mozilla/dom/GetUserMediaRequestBinding.h"
34 #include "mozilla/dom/MediaDeviceInfo.h"
35 #include "mozilla/dom/MediaDevices.h"
36 #include "mozilla/dom/MediaDevicesBinding.h"
37 #include "mozilla/dom/MediaStreamBinding.h"
38 #include "mozilla/dom/MediaStreamTrackBinding.h"
39 #include "mozilla/dom/Promise.h"
40 #include "mozilla/dom/UserActivation.h"
41 #include "mozilla/dom/WindowContext.h"
42 #include "mozilla/dom/WindowGlobalChild.h"
43 #include "mozilla/ipc/BackgroundChild.h"
44 #include "mozilla/ipc/PBackgroundChild.h"
45 #include "mozilla/media/CamerasTypes.h"
46 #include "mozilla/media/MediaChild.h"
47 #include "mozilla/media/MediaTaskUtils.h"
48 #include "nsAppDirectoryServiceDefs.h"
50 #include "nsContentUtils.h"
51 #include "nsGlobalWindowInner.h"
52 #include "nsHashPropertyBag.h"
53 #include "nsIEventTarget.h"
54 #include "nsIPermissionManager.h"
55 #include "nsIUUIDGenerator.h"
56 #include "nsJSUtils.h"
58 #include "nsNetUtil.h"
59 #include "nsProxyRelease.h"
64 /* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */
65 #include "MediaEngineFake.h"
66 #include "MediaEngineSource.h"
67 #if defined(MOZ_WEBRTC)
68 # include "MediaEngineWebRTC.h"
69 # include "MediaEngineWebRTCAudio.h"
70 # include "browser_logging/WebRtcLog.h"
71 # include "modules/audio_processing/include/audio_processing.h"
78 // A specialization of nsMainThreadPtrHolder for
79 // mozilla::dom::CallbackObjectHolder. See documentation for
80 // nsMainThreadPtrHolder in nsProxyRelease.h. This specialization lets us avoid
81 // wrapping the CallbackObjectHolder into a separate refcounted object.
82 template <class WebIDLCallbackT
, class XPCOMCallbackT
>
83 class nsMainThreadPtrHolder
<
84 mozilla::dom::CallbackObjectHolder
<WebIDLCallbackT
, XPCOMCallbackT
>>
86 typedef mozilla::dom::CallbackObjectHolder
<WebIDLCallbackT
, XPCOMCallbackT
>
90 nsMainThreadPtrHolder(const char* aName
, Holder
&& aHolder
)
91 : mHolder(std::move(aHolder
))
92 #ifndef RELEASE_OR_BETA
97 MOZ_ASSERT(NS_IsMainThread());
101 // We can be released on any thread.
102 ~nsMainThreadPtrHolder() {
103 if (NS_IsMainThread()) {
105 } else if (mHolder
.GetISupports()) {
106 nsCOMPtr
<nsIEventTarget
> target
= do_GetMainThread();
109 #ifdef RELEASE_OR_BETA
114 target
, mHolder
.Forget());
120 // Nobody should be touching the raw pointer off-main-thread.
121 if (MOZ_UNLIKELY(!NS_IsMainThread())) {
122 NS_ERROR("Can't dereference nsMainThreadPtrHolder off main thread");
128 bool operator!() const { return !mHolder
; }
130 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsMainThreadPtrHolder
<Holder
>)
136 #ifndef RELEASE_OR_BETA
137 const char* mName
= nullptr;
140 // Copy constructor and operator= not implemented. Once constructed, the
141 // holder is immutable.
142 Holder
& operator=(const nsMainThreadPtrHolder
& aOther
) = delete;
143 nsMainThreadPtrHolder(const nsMainThreadPtrHolder
& aOther
) = delete;
148 LazyLogModule
gMediaManagerLog("MediaManager");
149 #define LOG(...) MOZ_LOG(gMediaManagerLog, LogLevel::Debug, (__VA_ARGS__))
151 class GetUserMediaStreamTask
;
152 class LocalTrackSource
;
153 class SelectAudioOutputTask
;
155 using camera::CamerasAccessStatus
;
156 using dom::BFCacheStatus
;
157 using dom::CallerType
;
158 using dom::ConstrainDOMStringParameters
;
159 using dom::ConstrainDoubleRange
;
160 using dom::ConstrainLongRange
;
161 using dom::DisplayMediaStreamConstraints
;
164 using dom::FeaturePolicyUtils
;
166 using dom::GetUserMediaRequest
;
167 using dom::MediaDeviceKind
;
168 using dom::MediaDevices
;
169 using dom::MediaSourceEnum
;
170 using dom::MediaStreamConstraints
;
171 using dom::MediaStreamError
;
172 using dom::MediaStreamTrack
;
173 using dom::MediaStreamTrackSource
;
174 using dom::MediaTrackConstraints
;
175 using dom::MediaTrackConstraintSet
;
176 using dom::MediaTrackSettings
;
177 using dom::OwningBooleanOrMediaTrackConstraints
;
178 using dom::OwningStringOrStringSequence
;
179 using dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters
;
182 using dom::UserActivation
;
183 using dom::WindowGlobalChild
;
184 using ConstDeviceSetPromise
= MediaManager::ConstDeviceSetPromise
;
185 using DeviceSetPromise
= MediaManager::DeviceSetPromise
;
186 using LocalDevicePromise
= MediaManager::LocalDevicePromise
;
187 using LocalDeviceSetPromise
= MediaManager::LocalDeviceSetPromise
;
188 using LocalMediaDeviceSetRefCnt
= MediaManager::LocalMediaDeviceSetRefCnt
;
189 using MediaDeviceSetRefCnt
= MediaManager::MediaDeviceSetRefCnt
;
190 using media::NewRunnableFrom
;
191 using media::NewTaskFrom
;
192 using media::Refcountable
;
194 // Whether main thread actions of MediaManager shutdown (except for clearing
195 // of sSingleton) have completed.
196 static bool sHasMainThreadShutdown
;
199 DeviceState(RefPtr
<LocalMediaDevice
> aDevice
,
200 RefPtr
<LocalTrackSource
> aTrackSource
, bool aOffWhileDisabled
)
201 : mOffWhileDisabled(aOffWhileDisabled
),
202 mDevice(std::move(aDevice
)),
203 mTrackSource(std::move(aTrackSource
)) {
205 MOZ_ASSERT(mTrackSource
);
208 // true if we have stopped mDevice, this is a terminal state.
210 bool mStopped
= false;
212 // true if mDevice is currently enabled.
213 // A device must be both enabled and unmuted to be turned on and capturing.
215 bool mDeviceEnabled
= false;
217 // true if mDevice is currently muted.
218 // A device that is either muted or disabled is turned off and not capturing.
222 // true if the application has currently enabled mDevice.
224 bool mTrackEnabled
= false;
226 // Time when the application last enabled mDevice.
228 TimeStamp mTrackEnabledTime
;
230 // true if an operation to Start() or Stop() mDevice has been dispatched to
231 // the media thread and is not finished yet.
233 bool mOperationInProgress
= false;
235 // true if we are allowed to turn off the underlying source while all tracks
236 // are disabled. Only affects disabling; always turns off on user-agent mute.
238 bool mOffWhileDisabled
= false;
240 // Timer triggered by a MediaStreamTrackSource signaling that all tracks got
241 // disabled. When the timer fires we initiate Stop()ing mDevice.
242 // If set we allow dynamically stopping and starting mDevice.
244 const RefPtr
<MediaTimer
> mDisableTimer
= new MediaTimer();
246 // The underlying device we keep state for. Always non-null.
247 // Threadsafe access, but see method declarations for individual constraints.
248 const RefPtr
<LocalMediaDevice
> mDevice
;
250 // The MediaStreamTrackSource for any tracks (original and clones) originating
251 // from this device. Always non-null. Threadsafe access, but see method
252 // declarations for individual constraints.
253 const RefPtr
<LocalTrackSource
> mTrackSource
;
257 * This mimics the capture state from nsIMediaManagerService.
259 enum class CaptureState
: uint16_t {
260 Off
= nsIMediaManagerService::STATE_NOCAPTURE
,
261 Enabled
= nsIMediaManagerService::STATE_CAPTURE_ENABLED
,
262 Disabled
= nsIMediaManagerService::STATE_CAPTURE_DISABLED
,
265 static CaptureState
CombineCaptureState(CaptureState aFirst
,
266 CaptureState aSecond
) {
267 if (aFirst
== CaptureState::Enabled
|| aSecond
== CaptureState::Enabled
) {
268 return CaptureState::Enabled
;
270 if (aFirst
== CaptureState::Disabled
|| aSecond
== CaptureState::Disabled
) {
271 return CaptureState::Disabled
;
273 MOZ_ASSERT(aFirst
== CaptureState::Off
);
274 MOZ_ASSERT(aSecond
== CaptureState::Off
);
275 return CaptureState::Off
;
278 static uint16_t FromCaptureState(CaptureState aState
) {
279 MOZ_ASSERT(aState
== CaptureState::Off
|| aState
== CaptureState::Enabled
||
280 aState
== CaptureState::Disabled
);
281 return static_cast<uint16_t>(aState
);
284 void MediaManager::CallOnError(GetUserMediaErrorCallback
& aCallback
,
285 MediaStreamError
& aError
) {
286 aCallback
.Call(aError
);
289 void MediaManager::CallOnSuccess(GetUserMediaSuccessCallback
& aCallback
,
290 DOMMediaStream
& aStream
) {
291 aCallback
.Call(aStream
);
295 * DeviceListener has threadsafe refcounting for use across the main, media and
296 * MTG threads. But it has a non-threadsafe SupportsWeakPtr for WeakPtr usage
297 * only from main thread, to ensure that garbage- and cycle-collected objects
298 * don't hold a reference to it during late shutdown.
300 class DeviceListener
: public SupportsWeakPtr
{
302 typedef MozPromise
<bool /* aIgnored */, RefPtr
<MediaMgrError
>, true>
303 DeviceListenerPromise
;
305 NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(
311 * Registers this device listener as belonging to the given window listener.
312 * Stop() must be called on registered DeviceListeners before destruction.
314 void Register(GetUserMediaWindowListener
* aListener
);
317 * Marks this listener as active and creates the internal device state.
319 void Activate(RefPtr
<LocalMediaDevice
> aDevice
,
320 RefPtr
<LocalTrackSource
> aTrackSource
, bool aStartMuted
);
323 * Posts a task to initialize and start the associated device.
325 RefPtr
<DeviceListenerPromise
> InitializeAsync();
328 * Posts a task to stop the device associated with this DeviceListener and
329 * notifies the associated window listener that a track was stopped.
331 * This will also clean up the weak reference to the associated window
332 * listener, and tell the window listener to remove its hard reference to this
333 * DeviceListener, so any caller will need to keep its own hard ref.
338 * Gets the main thread MediaTrackSettings from the MediaEngineSource
339 * associated with aTrack.
341 void GetSettings(MediaTrackSettings
& aOutSettings
) const;
344 * Posts a task to set the enabled state of the device associated with this
345 * DeviceListener to aEnabled and notifies the associated window listener that
346 * a track's state has changed.
348 * Turning the hardware off while the device is disabled is supported for:
349 * - Camera (enabled by default, controlled by pref
350 * "media.getusermedia.camera.off_while_disabled.enabled")
351 * - Microphone (disabled by default, controlled by pref
352 * "media.getusermedia.microphone.off_while_disabled.enabled")
353 * Screen-, app-, or windowsharing is not supported at this time.
355 * The behavior is also different between disabling and enabling a device.
356 * While enabling is immediate, disabling only happens after a delay.
357 * This is now defaulting to 3 seconds but can be overriden by prefs:
358 * - "media.getusermedia.camera.off_while_disabled.delay_ms" and
359 * - "media.getusermedia.microphone.off_while_disabled.delay_ms".
361 * The delay is in place to prevent misuse by malicious sites. If a track is
362 * re-enabled before the delay has passed, the device will not be touched
363 * until another disable followed by the full delay happens.
365 void SetDeviceEnabled(bool aEnabled
);
368 * Posts a task to set the muted state of the device associated with this
369 * DeviceListener to aMuted and notifies the associated window listener that a
370 * track's state has changed.
372 * Turning the hardware off while the device is muted is supported for:
373 * - Camera (enabled by default, controlled by pref
374 * "media.getusermedia.camera.off_while_disabled.enabled")
375 * - Microphone (disabled by default, controlled by pref
376 * "media.getusermedia.microphone.off_while_disabled.enabled")
377 * Screen-, app-, or windowsharing is not supported at this time.
379 void SetDeviceMuted(bool aMuted
);
382 * Mutes or unmutes the associated video device if it is a camera.
384 void MuteOrUnmuteCamera(bool aMute
);
385 void MuteOrUnmuteMicrophone(bool aMute
);
387 LocalMediaDevice
* GetDevice() const {
388 return mDeviceState
? mDeviceState
->mDevice
.get() : nullptr;
391 bool Activated() const { return static_cast<bool>(mDeviceState
); }
393 bool Stopped() const { return mStopped
; }
395 bool CapturingVideo() const;
397 bool CapturingAudio() const;
399 CaptureState
CapturingSource(MediaSourceEnum aSource
) const;
401 RefPtr
<DeviceListenerPromise
> ApplyConstraints(
402 const MediaTrackConstraints
& aConstraints
, CallerType aCallerType
);
404 PrincipalHandle
GetPrincipalHandle() const;
406 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
407 size_t amount
= aMallocSizeOf(this);
408 // Assume mPrincipalHandle refers to a principal owned elsewhere.
409 // DeviceState does not have support for memory accounting.
414 virtual ~DeviceListener() {
415 MOZ_ASSERT(mStopped
);
416 MOZ_ASSERT(!mWindowListener
);
419 using DeviceOperationPromise
=
420 MozPromise
<nsresult
, bool, /* IsExclusive = */ true>;
423 * Posts a task to start or stop the device associated with aTrack, based on
424 * a passed-in boolean. Private method used by SetDeviceEnabled and
427 RefPtr
<DeviceOperationPromise
> UpdateDevice(bool aOn
);
429 // true after this listener has had all devices stopped. MainThread only.
432 // never ever indirect off this; just for assertions
433 PRThread
* mMainThreadCheck
;
435 // Set in Register() on main thread, then read from any thread.
436 PrincipalHandle mPrincipalHandle
;
438 // Weak pointer to the window listener that owns us. MainThread only.
439 GetUserMediaWindowListener
* mWindowListener
;
441 // Accessed from MediaTrackGraph thread, MediaManager thread, and MainThread
442 // No locking needed as it's set on Activate() and never assigned to again.
443 UniquePtr
<DeviceState
> mDeviceState
;
447 * This class represents a WindowID and handles all MediaTrackListeners
448 * (here subclassed as DeviceListeners) used to feed GetUserMedia tracks.
449 * It proxies feedback from them into messages for browser chrome.
450 * The DeviceListeners are used to Start() and Stop() the underlying
451 * MediaEngineSource when MediaStreams are assigned and deassigned in content.
453 class GetUserMediaWindowListener
{
457 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GetUserMediaWindowListener
)
459 // Create in an inactive state
460 GetUserMediaWindowListener(uint64_t aWindowID
,
461 const PrincipalHandle
& aPrincipalHandle
)
462 : mWindowID(aWindowID
),
463 mPrincipalHandle(aPrincipalHandle
),
464 mChromeNotificationTaskPosted(false) {}
467 * Registers an inactive gUM device listener for this WindowListener.
469 void Register(RefPtr
<DeviceListener
> aListener
) {
470 MOZ_ASSERT(NS_IsMainThread());
471 MOZ_ASSERT(aListener
);
472 MOZ_ASSERT(!aListener
->Activated());
473 MOZ_ASSERT(!mInactiveListeners
.Contains(aListener
), "Already registered");
474 MOZ_ASSERT(!mActiveListeners
.Contains(aListener
), "Already activated");
476 aListener
->Register(this);
477 mInactiveListeners
.AppendElement(std::move(aListener
));
481 * Activates an already registered and inactive gUM device listener for this
484 void Activate(RefPtr
<DeviceListener
> aListener
,
485 RefPtr
<LocalMediaDevice
> aDevice
,
486 RefPtr
<LocalTrackSource
> aTrackSource
) {
487 MOZ_ASSERT(NS_IsMainThread());
488 MOZ_ASSERT(aListener
);
489 MOZ_ASSERT(!aListener
->Activated());
490 MOZ_ASSERT(mInactiveListeners
.Contains(aListener
),
491 "Must be registered to activate");
492 MOZ_ASSERT(!mActiveListeners
.Contains(aListener
), "Already activated");
495 if (aDevice
->Kind() == MediaDeviceKind::Videoinput
) {
496 muted
= mCamerasAreMuted
;
497 } else if (aDevice
->Kind() == MediaDeviceKind::Audioinput
) {
498 muted
= mMicrophonesAreMuted
;
500 MOZ_CRASH("Unexpected device kind");
503 mInactiveListeners
.RemoveElement(aListener
);
504 aListener
->Activate(std::move(aDevice
), std::move(aTrackSource
), muted
);
505 mActiveListeners
.AppendElement(std::move(aListener
));
509 * Removes all DeviceListeners from this window listener.
510 * Removes this window listener from the list of active windows, so callers
511 * need to make sure to hold a strong reference.
514 MOZ_ASSERT(NS_IsMainThread());
516 for (auto& l
: mInactiveListeners
.Clone()) {
519 for (auto& l
: mActiveListeners
.Clone()) {
522 MOZ_ASSERT(mInactiveListeners
.Length() == 0);
523 MOZ_ASSERT(mActiveListeners
.Length() == 0);
525 MediaManager
* mgr
= MediaManager::GetIfExists();
527 MOZ_ASSERT(false, "MediaManager should stay until everything is removed");
530 GetUserMediaWindowListener
* windowListener
=
531 mgr
->GetWindowListener(mWindowID
);
533 if (!windowListener
) {
534 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
535 auto* globalWindow
= nsGlobalWindowInner::GetInnerWindowWithId(mWindowID
);
537 auto req
= MakeRefPtr
<GetUserMediaRequest
>(
538 globalWindow
, VoidString(), VoidString(),
539 UserActivation::IsHandlingUserInput());
540 obs
->NotifyWhenScriptSafe(req
, "recording-device-stopped", nullptr);
545 MOZ_ASSERT(windowListener
== this,
546 "There should only be one window listener per window ID");
548 LOG("GUMWindowListener %p removing windowID %" PRIu64
, this, mWindowID
);
549 mgr
->RemoveWindowID(mWindowID
);
553 * Removes a listener from our lists. Safe to call without holding a hard
554 * reference. That said, you'll still want to iterate on a copy of said lists,
555 * if you end up calling this method (or methods that may call this method) in
556 * the loop, to avoid inadvertently skipping members.
558 * For use only from GetUserMediaWindowListener and DeviceListener.
560 bool Remove(RefPtr
<DeviceListener
> aListener
) {
561 // We refcount aListener on entry since we're going to proxy-release it
562 // below to prevent the refcount going to zero on callers who might be
563 // inside the listener, but operating without a hard reference to self.
564 MOZ_ASSERT(NS_IsMainThread());
566 if (!mInactiveListeners
.RemoveElement(aListener
) &&
567 !mActiveListeners
.RemoveElement(aListener
)) {
570 MOZ_ASSERT(!mInactiveListeners
.Contains(aListener
),
571 "A DeviceListener should only be once in one of "
572 "mInactiveListeners and mActiveListeners");
573 MOZ_ASSERT(!mActiveListeners
.Contains(aListener
),
574 "A DeviceListener should only be once in one of "
575 "mInactiveListeners and mActiveListeners");
577 LOG("GUMWindowListener %p stopping DeviceListener %p.", this,
581 if (LocalMediaDevice
* removedDevice
= aListener
->GetDevice()) {
582 bool revokePermission
= true;
583 nsString removedRawId
;
584 nsString removedSourceType
;
585 removedDevice
->GetRawId(removedRawId
);
586 removedDevice
->GetMediaSource(removedSourceType
);
588 for (const auto& l
: mActiveListeners
) {
589 if (LocalMediaDevice
* device
= l
->GetDevice()) {
591 device
->GetRawId(rawId
);
592 if (removedRawId
.Equals(rawId
)) {
593 revokePermission
= false;
599 if (revokePermission
) {
600 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
601 auto* window
= nsGlobalWindowInner::GetInnerWindowWithId(mWindowID
);
602 auto req
= MakeRefPtr
<GetUserMediaRequest
>(
603 window
, removedRawId
, removedSourceType
,
604 UserActivation::IsHandlingUserInput());
605 obs
->NotifyWhenScriptSafe(req
, "recording-device-stopped", nullptr);
609 if (mInactiveListeners
.Length() == 0 && mActiveListeners
.Length() == 0) {
610 LOG("GUMWindowListener %p Removed last DeviceListener. Cleaning up.",
615 nsCOMPtr
<nsIEventTarget
> mainTarget
= do_GetMainThread();
616 // To allow being invoked by callers not holding a strong reference to self,
617 // hold the listener alive until the stack has unwound, by always
618 // dispatching a runnable (aAlwaysProxy = true)
619 NS_ProxyRelease(__func__
, mainTarget
, aListener
.forget(), true);
624 * Stops all screen/window/audioCapture sharing, but not camera or microphone.
628 void StopRawID(const nsString
& removedDeviceID
);
630 void MuteOrUnmuteCameras(bool aMute
);
631 void MuteOrUnmuteMicrophones(bool aMute
);
634 * Called by one of our DeviceListeners when one of its tracks has changed so
635 * that chrome state is affected.
636 * Schedules an event for the next stable state to update chrome.
638 void ChromeAffectingStateChanged();
641 * Called in stable state to send a notification to update chrome.
645 bool CapturingVideo() const {
646 MOZ_ASSERT(NS_IsMainThread());
647 for (auto& l
: mActiveListeners
) {
648 if (l
->CapturingVideo()) {
655 bool CapturingAudio() const {
656 MOZ_ASSERT(NS_IsMainThread());
657 for (auto& l
: mActiveListeners
) {
658 if (l
->CapturingAudio()) {
665 CaptureState
CapturingSource(MediaSourceEnum aSource
) const {
666 MOZ_ASSERT(NS_IsMainThread());
667 CaptureState result
= CaptureState::Off
;
668 for (auto& l
: mActiveListeners
) {
669 result
= CombineCaptureState(result
, l
->CapturingSource(aSource
));
674 RefPtr
<LocalMediaDeviceSetRefCnt
> GetDevices() {
675 RefPtr devices
= new LocalMediaDeviceSetRefCnt();
676 for (auto& l
: mActiveListeners
) {
677 devices
->AppendElement(l
->GetDevice());
682 uint64_t WindowID() const { return mWindowID
; }
684 PrincipalHandle
GetPrincipalHandle() const { return mPrincipalHandle
; }
686 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
687 size_t amount
= aMallocSizeOf(this);
688 // Assume mPrincipalHandle refers to a principal owned elsewhere.
689 amount
+= mInactiveListeners
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
690 for (const RefPtr
<DeviceListener
>& listener
: mInactiveListeners
) {
691 amount
+= listener
->SizeOfIncludingThis(aMallocSizeOf
);
693 amount
+= mActiveListeners
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
694 for (const RefPtr
<DeviceListener
>& listener
: mActiveListeners
) {
695 amount
+= listener
->SizeOfIncludingThis(aMallocSizeOf
);
701 ~GetUserMediaWindowListener() {
702 MOZ_ASSERT(mInactiveListeners
.Length() == 0,
703 "Inactive listeners should already be removed");
704 MOZ_ASSERT(mActiveListeners
.Length() == 0,
705 "Active listeners should already be removed");
709 const PrincipalHandle mPrincipalHandle
;
711 // true if we have scheduled a task to notify chrome in the next stable state.
712 // The task will reset this to false. MainThread only.
713 bool mChromeNotificationTaskPosted
;
715 nsTArray
<RefPtr
<DeviceListener
>> mInactiveListeners
;
716 nsTArray
<RefPtr
<DeviceListener
>> mActiveListeners
;
718 // Whether camera and microphone access in this window are currently
719 // User Agent (UA) muted. When true, new and cloned tracks must start
720 // out muted, to avoid JS circumventing UA mute. Per-camera and
721 // per-microphone UA muting is not supported.
722 bool mCamerasAreMuted
= false;
723 bool mMicrophonesAreMuted
= false;
726 class LocalTrackSource
: public MediaStreamTrackSource
{
728 LocalTrackSource(nsIPrincipal
* aPrincipal
, const nsString
& aLabel
,
729 const RefPtr
<DeviceListener
>& aListener
,
730 MediaSourceEnum aSource
, MediaTrack
* aTrack
,
731 RefPtr
<PeerIdentity
> aPeerIdentity
,
732 TrackingId aTrackingId
= TrackingId())
733 : MediaStreamTrackSource(aPrincipal
, aLabel
, std::move(aTrackingId
)),
736 mPeerIdentity(std::move(aPeerIdentity
)),
737 mListener(aListener
.get()) {}
739 MediaSourceEnum
GetMediaSource() const override
{ return mSource
; }
741 const PeerIdentity
* GetPeerIdentity() const override
{ return mPeerIdentity
; }
743 RefPtr
<MediaStreamTrackSource::ApplyConstraintsPromise
> ApplyConstraints(
744 const MediaTrackConstraints
& aConstraints
,
745 CallerType aCallerType
) override
{
746 MOZ_ASSERT(NS_IsMainThread());
747 if (sHasMainThreadShutdown
|| !mListener
) {
748 // Track has been stopped, or we are in shutdown. In either case
749 // there's no observable outcome, so pretend we succeeded.
750 return MediaStreamTrackSource::ApplyConstraintsPromise::CreateAndResolve(
753 return mListener
->ApplyConstraints(aConstraints
, aCallerType
);
756 void GetSettings(MediaTrackSettings
& aOutSettings
) override
{
758 mListener
->GetSettings(aOutSettings
);
762 void Stop() override
{
767 if (!mTrack
->IsDestroyed()) {
772 void Disable() override
{
774 mListener
->SetDeviceEnabled(false);
778 void Enable() override
{
780 mListener
->SetDeviceEnabled(true);
786 mTrack
->SetDisabledTrackMode(DisabledTrackMode::SILENCE_BLACK
);
791 mTrack
->SetDisabledTrackMode(DisabledTrackMode::ENABLED
);
794 const MediaSourceEnum mSource
;
795 const RefPtr
<MediaTrack
> mTrack
;
796 const RefPtr
<const PeerIdentity
> mPeerIdentity
;
799 ~LocalTrackSource() {
800 MOZ_ASSERT(NS_IsMainThread());
801 MOZ_ASSERT(mTrack
->IsDestroyed());
804 // This is a weak pointer to avoid having the DeviceListener (which may
805 // have references to threads and threadpools) kept alive by DOM-objects
806 // that may have ref-cycles and thus are released very late during
807 // shutdown, even after xpcom-shutdown-threads. See bug 1351655 for what
809 WeakPtr
<DeviceListener
> mListener
;
812 class AudioCaptureTrackSource
: public LocalTrackSource
{
814 AudioCaptureTrackSource(nsIPrincipal
* aPrincipal
, nsPIDOMWindowInner
* aWindow
,
815 const nsString
& aLabel
,
816 AudioCaptureTrack
* aAudioCaptureTrack
,
817 RefPtr
<PeerIdentity
> aPeerIdentity
)
818 : LocalTrackSource(aPrincipal
, aLabel
, nullptr,
819 MediaSourceEnum::AudioCapture
, aAudioCaptureTrack
,
820 std::move(aPeerIdentity
)),
822 mAudioCaptureTrack(aAudioCaptureTrack
) {
823 mAudioCaptureTrack
->Start();
824 mAudioCaptureTrack
->Graph()->RegisterCaptureTrackForWindow(
825 mWindow
->WindowID(), mAudioCaptureTrack
);
826 mWindow
->SetAudioCapture(true);
829 void Stop() override
{
830 MOZ_ASSERT(NS_IsMainThread());
831 if (!mAudioCaptureTrack
->IsDestroyed()) {
833 mWindow
->SetAudioCapture(false);
834 mAudioCaptureTrack
->Graph()->UnregisterCaptureTrackForWindow(
835 mWindow
->WindowID());
838 // LocalTrackSource destroys the track.
839 LocalTrackSource::Stop();
840 MOZ_ASSERT(mAudioCaptureTrack
->IsDestroyed());
843 ProcessedMediaTrack
* InputTrack() const { return mAudioCaptureTrack
.get(); }
846 ~AudioCaptureTrackSource() {
847 MOZ_ASSERT(NS_IsMainThread());
848 MOZ_ASSERT(mAudioCaptureTrack
->IsDestroyed());
851 RefPtr
<nsPIDOMWindowInner
> mWindow
;
852 const RefPtr
<AudioCaptureTrack
> mAudioCaptureTrack
;
856 * nsIMediaDevice implementation.
858 NS_IMPL_ISUPPORTS(LocalMediaDevice
, nsIMediaDevice
)
860 MediaDevice::MediaDevice(MediaEngine
* aEngine
, MediaSourceEnum aMediaSource
,
861 const nsString
& aRawName
, const nsString
& aRawID
,
862 const nsString
& aRawGroupID
, IsScary aIsScary
,
863 const OsPromptable canRequestOsLevelPrompt
,
864 const IsPlaceholder aIsPlaceholder
)
866 mAudioDeviceInfo(nullptr),
867 mMediaSource(aMediaSource
),
868 mKind(MediaEngineSource::IsVideo(aMediaSource
)
869 ? MediaDeviceKind::Videoinput
870 : MediaDeviceKind::Audioinput
),
871 mScary(aIsScary
== IsScary::Yes
),
872 mCanRequestOsLevelPrompt(canRequestOsLevelPrompt
== OsPromptable::Yes
),
873 mIsFake(mEngine
->IsFake()),
874 mIsPlaceholder(aIsPlaceholder
== IsPlaceholder::Yes
),
876 NS_ConvertASCIItoUTF16(dom::MediaDeviceKindValues::GetString(mKind
))),
878 mRawGroupID(aRawGroupID
),
883 MediaDevice::MediaDevice(MediaEngine
* aEngine
,
884 const RefPtr
<AudioDeviceInfo
>& aAudioDeviceInfo
,
885 const nsString
& aRawID
)
887 mAudioDeviceInfo(aAudioDeviceInfo
),
888 mMediaSource(mAudioDeviceInfo
->Type() == AudioDeviceInfo::TYPE_INPUT
889 ? MediaSourceEnum::Microphone
890 : MediaSourceEnum::Other
),
891 mKind(mMediaSource
== MediaSourceEnum::Microphone
892 ? MediaDeviceKind::Audioinput
893 : MediaDeviceKind::Audiooutput
),
895 mCanRequestOsLevelPrompt(false),
897 mIsPlaceholder(false),
899 NS_ConvertASCIItoUTF16(dom::MediaDeviceKindValues::GetString(mKind
))),
901 mRawGroupID(mAudioDeviceInfo
->GroupID()),
902 mRawName(mAudioDeviceInfo
->Name()) {}
905 RefPtr
<MediaDevice
> MediaDevice::CopyWithNewRawGroupId(
906 const RefPtr
<MediaDevice
>& aOther
, const nsString
& aRawGroupID
) {
907 MOZ_ASSERT(!aOther
->mAudioDeviceInfo
, "device not supported");
908 return new MediaDevice(aOther
->mEngine
, aOther
->mMediaSource
,
909 aOther
->mRawName
, aOther
->mRawID
, aRawGroupID
,
910 IsScary(aOther
->mScary
),
911 OsPromptable(aOther
->mCanRequestOsLevelPrompt
),
912 IsPlaceholder(aOther
->mIsPlaceholder
));
915 MediaDevice::~MediaDevice() = default;
917 LocalMediaDevice::LocalMediaDevice(RefPtr
<const MediaDevice
> aRawDevice
,
919 const nsString
& aGroupID
,
920 const nsString
& aName
)
921 : mRawDevice(std::move(aRawDevice
)),
925 MOZ_ASSERT(mRawDevice
);
929 * Helper functions that implement the constraints algorithm from
930 * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
934 bool LocalMediaDevice::StringsContain(
935 const OwningStringOrStringSequence
& aStrings
, nsString aN
) {
936 return aStrings
.IsString() ? aStrings
.GetAsString() == aN
937 : aStrings
.GetAsStringSequence().Contains(aN
);
941 uint32_t LocalMediaDevice::FitnessDistance(
942 nsString aN
, const ConstrainDOMStringParameters
& aParams
) {
943 if (aParams
.mExact
.WasPassed() &&
944 !StringsContain(aParams
.mExact
.Value(), aN
)) {
947 if (aParams
.mIdeal
.WasPassed() &&
948 !StringsContain(aParams
.mIdeal
.Value(), aN
)) {
954 // Binding code doesn't templatize well...
957 uint32_t LocalMediaDevice::FitnessDistance(
959 const OwningStringOrStringSequenceOrConstrainDOMStringParameters
&
961 if (aConstraint
.IsString()) {
962 ConstrainDOMStringParameters params
;
963 params
.mIdeal
.Construct();
964 params
.mIdeal
.Value().SetAsString() = aConstraint
.GetAsString();
965 return FitnessDistance(aN
, params
);
966 } else if (aConstraint
.IsStringSequence()) {
967 ConstrainDOMStringParameters params
;
968 params
.mIdeal
.Construct();
969 params
.mIdeal
.Value().SetAsStringSequence() =
970 aConstraint
.GetAsStringSequence();
971 return FitnessDistance(aN
, params
);
973 return FitnessDistance(aN
, aConstraint
.GetAsConstrainDOMStringParameters());
977 uint32_t LocalMediaDevice::GetBestFitnessDistance(
978 const nsTArray
<const NormalizedConstraintSet
*>& aConstraintSets
,
979 CallerType aCallerType
) {
980 MOZ_ASSERT(MediaManager::IsInMediaThread());
981 MOZ_ASSERT(GetMediaSource() != MediaSourceEnum::Other
);
983 bool isChrome
= aCallerType
== CallerType::System
;
984 const nsString
& id
= isChrome
? RawID() : mID
;
985 auto type
= GetMediaSource();
986 uint64_t distance
= 0;
987 if (!aConstraintSets
.IsEmpty()) {
988 if (isChrome
/* For the screen/window sharing preview */ ||
989 type
== MediaSourceEnum::Camera
||
990 type
== MediaSourceEnum::Microphone
) {
991 distance
+= uint64_t(MediaConstraintsHelper::FitnessDistance(
992 Some(id
), aConstraintSets
[0]->mDeviceId
)) +
993 uint64_t(MediaConstraintsHelper::FitnessDistance(
994 Some(mGroupID
), aConstraintSets
[0]->mGroupId
));
997 if (distance
< UINT32_MAX
) {
998 // Forward request to underlying object to interrogate per-mode
1000 distance
+= Source()->GetBestFitnessDistance(aConstraintSets
);
1002 return std::min
<uint64_t>(distance
, UINT32_MAX
);
1006 LocalMediaDevice::GetRawName(nsAString
& aName
) {
1007 MOZ_ASSERT(NS_IsMainThread());
1008 aName
.Assign(mRawDevice
->mRawName
);
1013 LocalMediaDevice::GetType(nsAString
& aType
) {
1014 MOZ_ASSERT(NS_IsMainThread());
1015 aType
.Assign(mRawDevice
->mType
);
1020 LocalMediaDevice::GetRawId(nsAString
& aID
) {
1021 MOZ_ASSERT(NS_IsMainThread());
1022 aID
.Assign(RawID());
1027 LocalMediaDevice::GetId(nsAString
& aID
) {
1028 MOZ_ASSERT(NS_IsMainThread());
1034 LocalMediaDevice::GetScary(bool* aScary
) {
1035 *aScary
= mRawDevice
->mScary
;
1040 LocalMediaDevice::GetCanRequestOsLevelPrompt(bool* aCanRequestOsLevelPrompt
) {
1041 *aCanRequestOsLevelPrompt
= mRawDevice
->mCanRequestOsLevelPrompt
;
1045 void LocalMediaDevice::GetSettings(MediaTrackSettings
& aOutSettings
) {
1046 MOZ_ASSERT(NS_IsMainThread());
1047 Source()->GetSettings(aOutSettings
);
1050 MediaEngineSource
* LocalMediaDevice::Source() {
1052 mSource
= mRawDevice
->mEngine
->CreateSource(mRawDevice
);
1057 const TrackingId
& LocalMediaDevice::GetTrackingId() const {
1058 return mSource
->GetTrackingId();
1061 // Threadsafe since mKind and mSource are const.
1063 LocalMediaDevice::GetMediaSource(nsAString
& aMediaSource
) {
1064 if (Kind() == MediaDeviceKind::Audiooutput
) {
1065 aMediaSource
.Truncate();
1067 aMediaSource
.AssignASCII(
1068 dom::MediaSourceEnumValues::GetString(GetMediaSource()));
1073 nsresult
LocalMediaDevice::Allocate(const MediaTrackConstraints
& aConstraints
,
1074 const MediaEnginePrefs
& aPrefs
,
1076 const char** aOutBadConstraint
) {
1077 MOZ_ASSERT(MediaManager::IsInMediaThread());
1079 // Mock failure for automated tests.
1080 if (IsFake() && aConstraints
.mDeviceId
.WasPassed() &&
1081 aConstraints
.mDeviceId
.Value().IsString() &&
1082 aConstraints
.mDeviceId
.Value().GetAsString().EqualsASCII("bad device")) {
1083 return NS_ERROR_FAILURE
;
1086 return Source()->Allocate(aConstraints
, aPrefs
, aWindowID
, aOutBadConstraint
);
1089 void LocalMediaDevice::SetTrack(const RefPtr
<MediaTrack
>& aTrack
,
1090 const PrincipalHandle
& aPrincipalHandle
) {
1091 MOZ_ASSERT(MediaManager::IsInMediaThread());
1092 Source()->SetTrack(aTrack
, aPrincipalHandle
);
1095 nsresult
LocalMediaDevice::Start() {
1096 MOZ_ASSERT(MediaManager::IsInMediaThread());
1097 MOZ_ASSERT(Source());
1098 return Source()->Start();
1101 nsresult
LocalMediaDevice::Reconfigure(
1102 const MediaTrackConstraints
& aConstraints
, const MediaEnginePrefs
& aPrefs
,
1103 const char** aOutBadConstraint
) {
1104 MOZ_ASSERT(MediaManager::IsInMediaThread());
1105 auto type
= GetMediaSource();
1106 if (type
== MediaSourceEnum::Camera
|| type
== MediaSourceEnum::Microphone
) {
1107 NormalizedConstraints
c(aConstraints
);
1108 if (MediaConstraintsHelper::FitnessDistance(Some(mID
), c
.mDeviceId
) ==
1110 *aOutBadConstraint
= "deviceId";
1111 return NS_ERROR_INVALID_ARG
;
1113 if (MediaConstraintsHelper::FitnessDistance(Some(mGroupID
), c
.mGroupId
) ==
1115 *aOutBadConstraint
= "groupId";
1116 return NS_ERROR_INVALID_ARG
;
1119 return Source()->Reconfigure(aConstraints
, aPrefs
, aOutBadConstraint
);
1122 nsresult
LocalMediaDevice::FocusOnSelectedSource() {
1123 MOZ_ASSERT(MediaManager::IsInMediaThread());
1124 return Source()->FocusOnSelectedSource();
1127 nsresult
LocalMediaDevice::Stop() {
1128 MOZ_ASSERT(MediaManager::IsInMediaThread());
1129 MOZ_ASSERT(mSource
);
1130 return mSource
->Stop();
1133 nsresult
LocalMediaDevice::Deallocate() {
1134 MOZ_ASSERT(MediaManager::IsInMediaThread());
1135 MOZ_ASSERT(mSource
);
1136 return mSource
->Deallocate();
1139 MediaSourceEnum
MediaDevice::GetMediaSource() const { return mMediaSource
; }
1141 static const MediaTrackConstraints
& GetInvariant(
1142 const OwningBooleanOrMediaTrackConstraints
& aUnion
) {
1143 static const MediaTrackConstraints empty
;
1144 return aUnion
.IsMediaTrackConstraints() ? aUnion
.GetAsMediaTrackConstraints()
1148 // Source getter returning full list
1150 static void GetMediaDevices(MediaEngine
* aEngine
, MediaSourceEnum aSrcType
,
1151 MediaManager::MediaDeviceSet
& aResult
,
1152 const char* aMediaDeviceName
= nullptr) {
1153 MOZ_ASSERT(MediaManager::IsInMediaThread());
1155 LOG("%s: aEngine=%p, aSrcType=%" PRIu8
", aMediaDeviceName=%s", __func__
,
1156 aEngine
, static_cast<uint8_t>(aSrcType
),
1157 aMediaDeviceName
? aMediaDeviceName
: "null");
1158 nsTArray
<RefPtr
<MediaDevice
>> devices
;
1159 aEngine
->EnumerateDevices(aSrcType
, MediaSinkEnum::Other
, &devices
);
1162 * We're allowing multiple tabs to access the same camera for parity
1163 * with Chrome. See bug 811757 for some of the issues surrounding
1164 * this decision. To disallow, we'd filter by IsAvailable() as we used
1167 if (aMediaDeviceName
&& *aMediaDeviceName
) {
1168 for (auto& device
: devices
) {
1169 if (device
->mRawName
.EqualsASCII(aMediaDeviceName
)) {
1170 aResult
.AppendElement(device
);
1171 LOG("%s: found aMediaDeviceName=%s", __func__
, aMediaDeviceName
);
1176 aResult
= std::move(devices
);
1177 if (MOZ_LOG_TEST(gMediaManagerLog
, mozilla::LogLevel::Debug
)) {
1178 for (auto& device
: aResult
) {
1179 LOG("%s: appending device=%s", __func__
,
1180 NS_ConvertUTF16toUTF8(device
->mRawName
).get());
1186 RefPtr
<LocalDeviceSetPromise
> MediaManager::SelectSettings(
1187 const MediaStreamConstraints
& aConstraints
, CallerType aCallerType
,
1188 RefPtr
<LocalMediaDeviceSetRefCnt
> aDevices
) {
1189 MOZ_ASSERT(NS_IsMainThread());
1191 // Algorithm accesses device capabilities code and must run on media thread.
1192 // Modifies passed-in aDevices.
1194 return MediaManager::Dispatch
<LocalDeviceSetPromise
>(
1195 __func__
, [aConstraints
, devices
= std::move(aDevices
),
1196 aCallerType
](MozPromiseHolder
<LocalDeviceSetPromise
>& holder
) {
1197 auto& devicesRef
= *devices
;
1199 // Since the advanced part of the constraints algorithm needs to know
1200 // when a candidate set is overconstrained (zero members), we must split
1201 // up the list into videos and audios, and put it back together again at
1204 nsTArray
<RefPtr
<LocalMediaDevice
>> videos
;
1205 nsTArray
<RefPtr
<LocalMediaDevice
>> audios
;
1207 for (const auto& device
: devicesRef
) {
1208 MOZ_ASSERT(device
->Kind() == MediaDeviceKind::Videoinput
||
1209 device
->Kind() == MediaDeviceKind::Audioinput
);
1210 if (device
->Kind() == MediaDeviceKind::Videoinput
) {
1211 videos
.AppendElement(device
);
1212 } else if (device
->Kind() == MediaDeviceKind::Audioinput
) {
1213 audios
.AppendElement(device
);
1217 const char* badConstraint
= nullptr;
1218 bool needVideo
= IsOn(aConstraints
.mVideo
);
1219 bool needAudio
= IsOn(aConstraints
.mAudio
);
1221 if (needVideo
&& videos
.Length()) {
1222 badConstraint
= MediaConstraintsHelper::SelectSettings(
1223 NormalizedConstraints(GetInvariant(aConstraints
.mVideo
)), videos
,
1226 if (!badConstraint
&& needAudio
&& audios
.Length()) {
1227 badConstraint
= MediaConstraintsHelper::SelectSettings(
1228 NormalizedConstraints(GetInvariant(aConstraints
.mAudio
)), audios
,
1231 if (badConstraint
) {
1232 LOG("SelectSettings: bad constraint found! Calling error handler!");
1233 nsString constraint
;
1234 constraint
.AssignASCII(badConstraint
);
1236 new MediaMgrError(MediaMgrError::Name::OverconstrainedError
, "",
1241 if (!needVideo
== !videos
.Length() && !needAudio
== !audios
.Length()) {
1242 for (auto& video
: videos
) {
1243 devicesRef
.AppendElement(video
);
1245 for (auto& audio
: audios
) {
1246 devicesRef
.AppendElement(audio
);
1249 holder
.Resolve(devices
, __func__
);
1254 * Describes a requested task that handles response from the UI and sends
1255 * results back to the DOM.
1257 class GetUserMediaTask
{
1259 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GetUserMediaTask
)
1260 GetUserMediaTask(uint64_t aWindowID
, const ipc::PrincipalInfo
& aPrincipalInfo
,
1261 CallerType aCallerType
)
1262 : mPrincipalInfo(aPrincipalInfo
),
1263 mWindowID(aWindowID
),
1264 mCallerType(aCallerType
) {}
1266 virtual void Denied(MediaMgrError::Name aName
,
1267 const nsCString
& aMessage
= ""_ns
) = 0;
1269 virtual GetUserMediaStreamTask
* AsGetUserMediaStreamTask() { return nullptr; }
1270 virtual SelectAudioOutputTask
* AsSelectAudioOutputTask() { return nullptr; }
1272 uint64_t GetWindowID() const { return mWindowID
; }
1273 enum CallerType
CallerType() const { return mCallerType
; }
1275 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
1276 size_t amount
= aMallocSizeOf(this);
1277 // Assume mWindowListener is owned by MediaManager.
1278 // Assume mAudioDeviceListener and mVideoDeviceListener are owned by
1280 // Assume PrincipalInfo string buffers are shared.
1281 // Member types without support for accounting of pointees:
1282 // MozPromiseHolder, RefPtr<LocalMediaDevice>.
1283 // We don't have a good way to account for lambda captures for MozPromise
1289 virtual ~GetUserMediaTask() = default;
1291 // Call GetPrincipalKey again, if not private browing, this time with
1292 // persist = true, to promote deviceIds to persistent, in case they're not
1293 // already. Fire'n'forget.
1294 void PersistPrincipalKey() {
1295 if (IsPrincipalInfoPrivate(mPrincipalInfo
)) {
1298 media::GetPrincipalKey(mPrincipalInfo
, true)
1300 GetCurrentSerialEventTarget(), __func__
,
1301 [](const media::PrincipalKeyPromise::ResolveOrRejectValue
& aValue
) {
1302 if (aValue
.IsReject()) {
1303 LOG("Failed get Principal key. Persisting of deviceIds "
1310 // Thread-safe (object) principal of Window with ID mWindowID
1311 const ipc::PrincipalInfo mPrincipalInfo
;
1314 // The ID of the not-necessarily-toplevel inner Window relevant global
1315 // object of the MediaDevices on which getUserMedia() was called
1316 const uint64_t mWindowID
;
1317 // Whether the JS caller of getUserMedia() has system (subject) principal
1318 const enum CallerType mCallerType
;
1322 * Describes a requested task that handles response from the UI to a
1323 * getUserMedia() request and sends results back to content. If the request
1324 * is allowed and device initialization succeeds, then the MozPromise is
1325 * resolved with a DOMMediaStream having a track or tracks for the approved
1326 * device or devices.
1328 class GetUserMediaStreamTask final
: public GetUserMediaTask
{
1330 GetUserMediaStreamTask(
1331 const MediaStreamConstraints
& aConstraints
,
1332 MozPromiseHolder
<MediaManager::StreamPromise
>&& aHolder
,
1333 uint64_t aWindowID
, RefPtr
<GetUserMediaWindowListener
> aWindowListener
,
1334 RefPtr
<DeviceListener
> aAudioDeviceListener
,
1335 RefPtr
<DeviceListener
> aVideoDeviceListener
,
1336 const MediaEnginePrefs
& aPrefs
, const ipc::PrincipalInfo
& aPrincipalInfo
,
1337 enum CallerType aCallerType
, bool aShouldFocusSource
)
1338 : GetUserMediaTask(aWindowID
, aPrincipalInfo
, aCallerType
),
1339 mConstraints(aConstraints
),
1340 mHolder(std::move(aHolder
)),
1341 mWindowListener(std::move(aWindowListener
)),
1342 mAudioDeviceListener(std::move(aAudioDeviceListener
)),
1343 mVideoDeviceListener(std::move(aVideoDeviceListener
)),
1345 mShouldFocusSource(aShouldFocusSource
),
1346 mManager(MediaManager::GetInstance()) {}
1348 void Allowed(RefPtr
<LocalMediaDevice
> aAudioDevice
,
1349 RefPtr
<LocalMediaDevice
> aVideoDevice
) {
1350 MOZ_ASSERT(aAudioDevice
|| aVideoDevice
);
1351 mAudioDevice
= std::move(aAudioDevice
);
1352 mVideoDevice
= std::move(aVideoDevice
);
1353 // Reuse the same thread to save memory.
1354 MediaManager::Dispatch(
1355 NewRunnableMethod("GetUserMediaStreamTask::AllocateDevices", this,
1356 &GetUserMediaStreamTask::AllocateDevices
));
1359 GetUserMediaStreamTask
* AsGetUserMediaStreamTask() override
{ return this; }
1362 ~GetUserMediaStreamTask() override
{
1363 if (!mHolder
.IsEmpty()) {
1364 Fail(MediaMgrError::Name::NotAllowedError
);
1368 void Fail(MediaMgrError::Name aName
, const nsCString
& aMessage
= ""_ns
,
1369 const nsString
& aConstraint
= u
""_ns
) {
1370 mHolder
.Reject(MakeRefPtr
<MediaMgrError
>(aName
, aMessage
, aConstraint
),
1372 // We add a disabled listener to the StreamListeners array until accepted
1373 // If this was the only active MediaStream, remove the window from the list.
1374 NS_DispatchToMainThread(NS_NewRunnableFunction(
1375 "DeviceListener::Stop",
1376 [audio
= mAudioDeviceListener
, video
= mVideoDeviceListener
] {
1387 * Runs on a separate thread and is responsible for allocating devices.
1389 * Do not run this on the main thread.
1391 void AllocateDevices() {
1392 MOZ_ASSERT(!NS_IsMainThread());
1393 LOG("GetUserMediaStreamTask::AllocateDevices()");
1395 // Allocate a video or audio device and return a MediaStream via
1396 // PrepareDOMStream().
1399 const char* errorMsg
= nullptr;
1400 const char* badConstraint
= nullptr;
1403 auto& constraints
= GetInvariant(mConstraints
.mAudio
);
1404 rv
= mAudioDevice
->Allocate(constraints
, mPrefs
, mWindowID
,
1406 if (NS_FAILED(rv
)) {
1407 errorMsg
= "Failed to allocate audiosource";
1408 if (rv
== NS_ERROR_NOT_AVAILABLE
&& !badConstraint
) {
1409 nsTArray
<RefPtr
<LocalMediaDevice
>> devices
;
1410 devices
.AppendElement(mAudioDevice
);
1411 badConstraint
= MediaConstraintsHelper::SelectSettings(
1412 NormalizedConstraints(constraints
), devices
, mCallerType
);
1416 if (!errorMsg
&& mVideoDevice
) {
1417 auto& constraints
= GetInvariant(mConstraints
.mVideo
);
1418 rv
= mVideoDevice
->Allocate(constraints
, mPrefs
, mWindowID
,
1420 if (NS_FAILED(rv
)) {
1421 errorMsg
= "Failed to allocate videosource";
1422 if (rv
== NS_ERROR_NOT_AVAILABLE
&& !badConstraint
) {
1423 nsTArray
<RefPtr
<LocalMediaDevice
>> devices
;
1424 devices
.AppendElement(mVideoDevice
);
1425 badConstraint
= MediaConstraintsHelper::SelectSettings(
1426 NormalizedConstraints(constraints
), devices
, mCallerType
);
1429 mAudioDevice
->Deallocate();
1432 mVideoTrackingId
.emplace(mVideoDevice
->GetTrackingId());
1436 LOG("%s %" PRIu32
, errorMsg
, static_cast<uint32_t>(rv
));
1437 if (badConstraint
) {
1438 Fail(MediaMgrError::Name::OverconstrainedError
, ""_ns
,
1439 NS_ConvertUTF8toUTF16(badConstraint
));
1441 Fail(MediaMgrError::Name::NotReadableError
, nsCString(errorMsg
));
1443 NS_DispatchToMainThread(
1444 NS_NewRunnableFunction("MediaManager::SendPendingGUMRequest", []() {
1445 if (MediaManager
* manager
= MediaManager::GetIfExists()) {
1446 manager
->SendPendingGUMRequest();
1451 NS_DispatchToMainThread(
1452 NewRunnableMethod("GetUserMediaStreamTask::PrepareDOMStream", this,
1453 &GetUserMediaStreamTask::PrepareDOMStream
));
1457 void Denied(MediaMgrError::Name aName
, const nsCString
& aMessage
) override
{
1458 MOZ_ASSERT(NS_IsMainThread());
1459 Fail(aName
, aMessage
);
1462 const MediaStreamConstraints
& GetConstraints() { return mConstraints
; }
1465 void PrepareDOMStream();
1467 // Constraints derived from those passed to getUserMedia() but adjusted for
1468 // preferences, defaults, and security
1469 const MediaStreamConstraints mConstraints
;
1471 MozPromiseHolder
<MediaManager::StreamPromise
> mHolder
;
1472 // GetUserMediaWindowListener with which DeviceListeners are registered
1473 const RefPtr
<GetUserMediaWindowListener
> mWindowListener
;
1474 const RefPtr
<DeviceListener
> mAudioDeviceListener
;
1475 const RefPtr
<DeviceListener
> mVideoDeviceListener
;
1476 // MediaDevices are set when selected and Allowed() by the UI.
1477 RefPtr
<LocalMediaDevice
> mAudioDevice
;
1478 RefPtr
<LocalMediaDevice
> mVideoDevice
;
1479 // Tracking id unique for a video frame source. Set when the corresponding
1480 // device has been allocated.
1481 Maybe
<TrackingId
> mVideoTrackingId
;
1482 // Copy of MediaManager::mPrefs
1483 const MediaEnginePrefs mPrefs
;
1484 // media.getusermedia.window.focus_source.enabled
1485 const bool mShouldFocusSource
;
1486 // The MediaManager is referenced at construction so that it won't be
1487 // created after its ShutdownBlocker would run.
1488 const RefPtr
<MediaManager
> mManager
;
1492 * Creates a MediaTrack, attaches a listener and resolves a MozPromise to
1493 * provide the stream to the DOM.
1495 * All of this must be done on the main thread!
1497 void GetUserMediaStreamTask::PrepareDOMStream() {
1498 MOZ_ASSERT(NS_IsMainThread());
1499 LOG("GetUserMediaStreamTask::PrepareDOMStream()");
1500 nsGlobalWindowInner
* window
=
1501 nsGlobalWindowInner::GetInnerWindowWithId(mWindowID
);
1503 // We're on main-thread, and the windowlist can only
1504 // be invalidated from the main-thread (see OnNavigation)
1505 if (!mManager
->IsWindowListenerStillActive(mWindowListener
)) {
1506 // This window is no longer live. mListener has already been removed.
1510 MediaTrackGraph::GraphDriverType graphDriverType
=
1511 mAudioDevice
? MediaTrackGraph::AUDIO_THREAD_DRIVER
1512 : MediaTrackGraph::SYSTEM_THREAD_DRIVER
;
1513 MediaTrackGraph
* mtg
= MediaTrackGraph::GetInstance(
1514 graphDriverType
, window
, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE
,
1515 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE
);
1517 auto domStream
= MakeRefPtr
<DOMMediaStream
>(window
);
1518 RefPtr
<LocalTrackSource
> audioTrackSource
;
1519 RefPtr
<LocalTrackSource
> videoTrackSource
;
1520 nsCOMPtr
<nsIPrincipal
> principal
;
1521 RefPtr
<PeerIdentity
> peerIdentity
= nullptr;
1522 if (!mConstraints
.mPeerIdentity
.IsEmpty()) {
1523 peerIdentity
= new PeerIdentity(mConstraints
.mPeerIdentity
);
1524 principal
= NullPrincipal::CreateWithInheritedAttributes(
1525 window
->GetExtantDoc()->NodePrincipal());
1527 principal
= window
->GetExtantDoc()->NodePrincipal();
1529 RefPtr
<GenericNonExclusivePromise
> firstFramePromise
;
1531 if (mAudioDevice
->GetMediaSource() == MediaSourceEnum::AudioCapture
) {
1532 // AudioCapture is a special case, here, in the sense that we're not
1533 // really using the audio source and the SourceMediaTrack, which acts
1534 // as placeholders. We re-route a number of tracks internally in the
1535 // MTG and mix them down instead.
1537 "MediaCaptureWindowState doesn't handle "
1538 "MediaSourceEnum::AudioCapture. This must be fixed with UX "
1539 "before shipping.");
1540 auto audioCaptureSource
= MakeRefPtr
<AudioCaptureTrackSource
>(
1541 principal
, window
, u
"Window audio capture"_ns
,
1542 mtg
->CreateAudioCaptureTrack(), peerIdentity
);
1543 audioTrackSource
= audioCaptureSource
;
1544 RefPtr
<MediaStreamTrack
> track
= new dom::AudioStreamTrack(
1545 window
, audioCaptureSource
->InputTrack(), audioCaptureSource
);
1546 domStream
->AddTrackInternal(track
);
1548 const nsString
& audioDeviceName
= mAudioDevice
->mName
;
1549 RefPtr
<MediaTrack
> track
;
1551 if (mAudioDevice
->IsFake()) {
1552 track
= mtg
->CreateSourceTrack(MediaSegment::AUDIO
);
1554 track
= AudioProcessingTrack::Create(mtg
);
1555 track
->Suspend(); // Microphone source resumes in SetTrack
1558 track
= mtg
->CreateSourceTrack(MediaSegment::AUDIO
);
1560 audioTrackSource
= new LocalTrackSource(
1561 principal
, audioDeviceName
, mAudioDeviceListener
,
1562 mAudioDevice
->GetMediaSource(), track
, peerIdentity
);
1563 MOZ_ASSERT(MediaManager::IsOn(mConstraints
.mAudio
));
1564 RefPtr
<MediaStreamTrack
> domTrack
= new dom::AudioStreamTrack(
1565 window
, track
, audioTrackSource
, dom::MediaStreamTrackState::Live
,
1566 false, GetInvariant(mConstraints
.mAudio
));
1567 domStream
->AddTrackInternal(domTrack
);
1571 const nsString
& videoDeviceName
= mVideoDevice
->mName
;
1572 RefPtr
<MediaTrack
> track
= mtg
->CreateSourceTrack(MediaSegment::VIDEO
);
1573 videoTrackSource
= new LocalTrackSource(
1574 principal
, videoDeviceName
, mVideoDeviceListener
,
1575 mVideoDevice
->GetMediaSource(), track
, peerIdentity
, *mVideoTrackingId
);
1576 MOZ_ASSERT(MediaManager::IsOn(mConstraints
.mVideo
));
1577 RefPtr
<MediaStreamTrack
> domTrack
= new dom::VideoStreamTrack(
1578 window
, track
, videoTrackSource
, dom::MediaStreamTrackState::Live
,
1579 false, GetInvariant(mConstraints
.mVideo
));
1580 domStream
->AddTrackInternal(domTrack
);
1581 switch (mVideoDevice
->GetMediaSource()) {
1582 case MediaSourceEnum::Browser
:
1583 case MediaSourceEnum::Screen
:
1584 case MediaSourceEnum::Window
:
1585 // Wait for first frame for screen-sharing devices, to ensure
1586 // with and height settings are available immediately, to pass wpt.
1587 firstFramePromise
= mVideoDevice
->Source()->GetFirstFramePromise();
1594 if (!domStream
|| (!audioTrackSource
&& !videoTrackSource
) ||
1595 sHasMainThreadShutdown
) {
1596 LOG("Returning error for getUserMedia() - no stream");
1599 MakeRefPtr
<MediaMgrError
>(
1600 MediaMgrError::Name::AbortError
,
1601 sHasMainThreadShutdown
? "In shutdown"_ns
: "No stream."_ns
),
1606 // Activate our device listeners. We'll call Start() on the source when we
1607 // get a callback that the MediaStream has started consuming. The listener
1608 // is freed when the page is invalidated (on navigation or close).
1609 if (mAudioDeviceListener
) {
1610 mWindowListener
->Activate(mAudioDeviceListener
, mAudioDevice
,
1611 std::move(audioTrackSource
));
1613 if (mVideoDeviceListener
) {
1614 mWindowListener
->Activate(mVideoDeviceListener
, mVideoDevice
,
1615 std::move(videoTrackSource
));
1618 // Dispatch to the media thread to ask it to start the sources, because that
1619 // can take a while.
1620 typedef DeviceListener::DeviceListenerPromise PromiseType
;
1621 AutoTArray
<RefPtr
<PromiseType
>, 2> promises
;
1622 if (mAudioDeviceListener
) {
1623 promises
.AppendElement(mAudioDeviceListener
->InitializeAsync());
1625 if (mVideoDeviceListener
) {
1626 promises
.AppendElement(mVideoDeviceListener
->InitializeAsync());
1628 PromiseType::All(GetMainThreadSerialEventTarget(), promises
)
1630 GetMainThreadSerialEventTarget(), __func__
,
1631 [manager
= mManager
, windowListener
= mWindowListener
,
1632 firstFramePromise
] {
1633 LOG("GetUserMediaStreamTask::PrepareDOMStream: starting success "
1634 "callback following InitializeAsync()");
1635 // Initiating and starting devices succeeded.
1636 windowListener
->ChromeAffectingStateChanged();
1637 manager
->SendPendingGUMRequest();
1638 if (!firstFramePromise
) {
1639 return DeviceListener::DeviceListenerPromise::CreateAndResolve(
1642 RefPtr
<DeviceListener::DeviceListenerPromise
> resolvePromise
=
1643 firstFramePromise
->Then(
1644 GetMainThreadSerialEventTarget(), __func__
,
1646 return DeviceListener::DeviceListenerPromise::
1647 CreateAndResolve(true, __func__
);
1650 return DeviceListener::DeviceListenerPromise::
1651 CreateAndReject(MakeRefPtr
<MediaMgrError
>(
1652 MediaMgrError::Name::AbortError
,
1656 return resolvePromise
;
1658 [audio
= mAudioDeviceListener
,
1659 video
= mVideoDeviceListener
](RefPtr
<MediaMgrError
>&& aError
) {
1660 LOG("GetUserMediaStreamTask::PrepareDOMStream: starting failure "
1661 "callback following InitializeAsync()");
1668 return DeviceListener::DeviceListenerPromise::CreateAndReject(
1672 GetMainThreadSerialEventTarget(), __func__
,
1673 [holder
= std::move(mHolder
), domStream
, callerType
= mCallerType
,
1674 shouldFocus
= mShouldFocusSource
, videoDevice
= mVideoDevice
](
1675 const DeviceListener::DeviceListenerPromise::ResolveOrRejectValue
&
1677 if (aValue
.IsResolve()) {
1678 if (auto* mgr
= MediaManager::GetIfExists();
1679 mgr
&& !sHasMainThreadShutdown
&& videoDevice
&&
1680 callerType
== CallerType::NonSystem
&& shouldFocus
) {
1681 // Device was successfully started. Attempt to focus the
1683 MOZ_ALWAYS_SUCCEEDS(
1684 mgr
->mMediaThread
->Dispatch(NS_NewRunnableFunction(
1685 "GetUserMediaStreamTask::FocusOnSelectedSource",
1686 [videoDevice
= std::move(videoDevice
)] {
1687 nsresult rv
= videoDevice
->FocusOnSelectedSource();
1688 if (NS_FAILED(rv
)) {
1689 LOG("FocusOnSelectedSource failed");
1694 holder
.Resolve(domStream
, __func__
);
1696 holder
.Reject(aValue
.RejectValue(), __func__
);
1700 PersistPrincipalKey();
1704 * Describes a requested task that handles response from the UI to a
1705 * selectAudioOutput() request and sends results back to content. If the
1706 * request is allowed, then the MozPromise is resolved with a MediaDevice
1707 * for the approved device.
1709 class SelectAudioOutputTask final
: public GetUserMediaTask
{
1711 SelectAudioOutputTask(MozPromiseHolder
<LocalDevicePromise
>&& aHolder
,
1712 uint64_t aWindowID
, enum CallerType aCallerType
,
1713 const ipc::PrincipalInfo
& aPrincipalInfo
)
1714 : GetUserMediaTask(aWindowID
, aPrincipalInfo
, aCallerType
),
1715 mHolder(std::move(aHolder
)) {}
1717 void Allowed(RefPtr
<LocalMediaDevice
> aAudioOutput
) {
1718 MOZ_ASSERT(aAudioOutput
);
1719 mHolder
.Resolve(std::move(aAudioOutput
), __func__
);
1720 PersistPrincipalKey();
1723 void Denied(MediaMgrError::Name aName
, const nsCString
& aMessage
) override
{
1724 MOZ_ASSERT(NS_IsMainThread());
1725 Fail(aName
, aMessage
);
1728 SelectAudioOutputTask
* AsSelectAudioOutputTask() override
{ return this; }
1731 ~SelectAudioOutputTask() override
{
1732 if (!mHolder
.IsEmpty()) {
1733 Fail(MediaMgrError::Name::NotAllowedError
);
1737 void Fail(MediaMgrError::Name aName
, const nsCString
& aMessage
= ""_ns
) {
1738 mHolder
.Reject(MakeRefPtr
<MediaMgrError
>(aName
, aMessage
), __func__
);
1742 MozPromiseHolder
<LocalDevicePromise
> mHolder
;
1746 void MediaManager::GuessVideoDeviceGroupIDs(MediaDeviceSet
& aDevices
,
1747 const MediaDeviceSet
& aAudios
) {
1748 // Run the logic in a lambda to avoid duplication.
1749 auto updateGroupIdIfNeeded
= [&](RefPtr
<MediaDevice
>& aVideo
,
1750 const MediaDeviceKind aKind
) -> bool {
1751 MOZ_ASSERT(aVideo
->mKind
== MediaDeviceKind::Videoinput
);
1752 MOZ_ASSERT(aKind
== MediaDeviceKind::Audioinput
||
1753 aKind
== MediaDeviceKind::Audiooutput
);
1754 // This will store the new group id if a match is found.
1755 nsString newVideoGroupID
;
1756 // If the group id needs to be updated this will become true. It is
1757 // necessary when the new group id is an empty string. Without this extra
1758 // variable to signal the update, we would resort to test if
1759 // `newVideoGroupId` is empty. However,
1760 // that check does not work when the new group id is an empty string.
1761 bool updateGroupId
= false;
1762 for (const RefPtr
<MediaDevice
>& dev
: aAudios
) {
1763 if (dev
->mKind
!= aKind
) {
1766 if (!FindInReadable(aVideo
->mRawName
, dev
->mRawName
)) {
1769 if (newVideoGroupID
.IsEmpty()) {
1770 // This is only expected on first match. If that's the only match group
1771 // id will be updated to this one at the end of the loop.
1772 updateGroupId
= true;
1773 newVideoGroupID
= dev
->mRawGroupID
;
1775 // More than one device found, it is impossible to know which group id
1776 // is the correct one.
1777 updateGroupId
= false;
1778 newVideoGroupID
= u
""_ns
;
1782 if (updateGroupId
) {
1783 aVideo
= MediaDevice::CopyWithNewRawGroupId(aVideo
, newVideoGroupID
);
1789 for (RefPtr
<MediaDevice
>& video
: aDevices
) {
1790 if (video
->mKind
!= MediaDeviceKind::Videoinput
) {
1793 if (updateGroupIdIfNeeded(video
, MediaDeviceKind::Audioinput
)) {
1794 // GroupId has been updated, continue to the next video device
1797 // GroupId has not been updated, check among the outputs
1798 updateGroupIdIfNeeded(video
, MediaDeviceKind::Audiooutput
);
1804 // Class to hold the promise used to request device access and to resolve
1805 // even if |task| does not run, either because GeckoViewPermissionProcessChild
1806 // gets destroyed before ask-device-permission receives its
1807 // got-device-permission reply, or because the media thread is no longer
1808 // available. In either case, the process is shutting down so the result is
1809 // not important. Reject with a dummy error so the following Then-handler can
1810 // resolve with an empty set, so that callers do not need to handle rejection.
1811 class DeviceAccessRequestPromiseHolderWithFallback
1812 : public MozPromiseHolder
<MozPromise
<
1813 CamerasAccessStatus
, mozilla::ipc::ResponseRejectReason
, true>> {
1815 DeviceAccessRequestPromiseHolderWithFallback() = default;
1816 DeviceAccessRequestPromiseHolderWithFallback(
1817 DeviceAccessRequestPromiseHolderWithFallback
&&) = default;
1818 ~DeviceAccessRequestPromiseHolderWithFallback() {
1820 Reject(ipc::ResponseRejectReason::ChannelClosed
, __func__
);
1825 } // anonymous namespace
1827 MediaManager::DeviceEnumerationParams::DeviceEnumerationParams(
1828 dom::MediaSourceEnum aInputType
, DeviceType aType
,
1829 nsAutoCString aForcedDeviceName
)
1830 : mInputType(aInputType
),
1832 mForcedDeviceName(std::move(aForcedDeviceName
)) {
1833 MOZ_ASSERT(NS_IsMainThread());
1834 MOZ_ASSERT(mInputType
!= dom::MediaSourceEnum::Other
);
1835 MOZ_ASSERT_IF(!mForcedDeviceName
.IsEmpty(), mType
== DeviceType::Real
);
1838 MediaManager::VideoDeviceEnumerationParams::VideoDeviceEnumerationParams(
1839 dom::MediaSourceEnum aInputType
, DeviceType aType
,
1840 nsAutoCString aForcedDeviceName
, nsAutoCString aForcedMicrophoneName
)
1841 : DeviceEnumerationParams(aInputType
, aType
, std::move(aForcedDeviceName
)),
1842 mForcedMicrophoneName(std::move(aForcedMicrophoneName
)) {
1843 MOZ_ASSERT(NS_IsMainThread());
1844 MOZ_ASSERT_IF(!mForcedMicrophoneName
.IsEmpty(),
1845 mInputType
== dom::MediaSourceEnum::Camera
);
1846 MOZ_ASSERT_IF(!mForcedMicrophoneName
.IsEmpty(), mType
== DeviceType::Real
);
1849 MediaManager::EnumerationParams::EnumerationParams(
1850 EnumerationFlags aFlags
, Maybe
<VideoDeviceEnumerationParams
> aVideo
,
1851 Maybe
<DeviceEnumerationParams
> aAudio
)
1852 : mFlags(aFlags
), mVideo(std::move(aVideo
)), mAudio(std::move(aAudio
)) {
1853 MOZ_ASSERT(NS_IsMainThread());
1854 MOZ_ASSERT_IF(mVideo
, MediaEngineSource::IsVideo(mVideo
->mInputType
));
1855 MOZ_ASSERT_IF(mVideo
&& !mVideo
->mForcedDeviceName
.IsEmpty(),
1856 mVideo
->mInputType
== dom::MediaSourceEnum::Camera
);
1857 MOZ_ASSERT_IF(mVideo
&& mVideo
->mType
== DeviceType::Fake
,
1858 mVideo
->mInputType
== dom::MediaSourceEnum::Camera
);
1859 MOZ_ASSERT_IF(mAudio
, MediaEngineSource::IsAudio(mAudio
->mInputType
));
1860 MOZ_ASSERT_IF(mAudio
&& !mAudio
->mForcedDeviceName
.IsEmpty(),
1861 mAudio
->mInputType
== dom::MediaSourceEnum::Microphone
);
1862 MOZ_ASSERT_IF(mAudio
&& mAudio
->mType
== DeviceType::Fake
,
1863 mAudio
->mInputType
== dom::MediaSourceEnum::Microphone
);
1866 bool MediaManager::EnumerationParams::HasFakeCams() const {
1868 .map([](const auto& aDev
) { return aDev
.mType
== DeviceType::Fake
; })
1872 bool MediaManager::EnumerationParams::HasFakeMics() const {
1874 .map([](const auto& aDev
) { return aDev
.mType
== DeviceType::Fake
; })
1878 bool MediaManager::EnumerationParams::RealDeviceRequested() const {
1879 auto isReal
= [](const auto& aDev
) { return aDev
.mType
== DeviceType::Real
; };
1880 return mVideo
.map(isReal
).valueOr(false) ||
1881 mAudio
.map(isReal
).valueOr(false) ||
1882 mFlags
.contains(EnumerationFlag::EnumerateAudioOutputs
);
1885 MediaSourceEnum
MediaManager::EnumerationParams::VideoInputType() const {
1886 return mVideo
.map([](const auto& aDev
) { return aDev
.mInputType
; })
1887 .valueOr(MediaSourceEnum::Other
);
1890 MediaSourceEnum
MediaManager::EnumerationParams::AudioInputType() const {
1891 return mAudio
.map([](const auto& aDev
) { return aDev
.mInputType
; })
1892 .valueOr(MediaSourceEnum::Other
);
1895 /* static */ MediaManager::EnumerationParams
1896 MediaManager::CreateEnumerationParams(dom::MediaSourceEnum aVideoInputType
,
1897 dom::MediaSourceEnum aAudioInputType
,
1898 EnumerationFlags aFlags
) {
1899 MOZ_ASSERT(NS_IsMainThread());
1900 MOZ_ASSERT_IF(!MediaEngineSource::IsVideo(aVideoInputType
),
1901 aVideoInputType
== dom::MediaSourceEnum::Other
);
1902 MOZ_ASSERT_IF(!MediaEngineSource::IsAudio(aAudioInputType
),
1903 aAudioInputType
== dom::MediaSourceEnum::Other
);
1904 const bool forceFakes
= aFlags
.contains(EnumerationFlag::ForceFakes
);
1905 const bool fakeByPref
= Preferences::GetBool("media.navigator.streams.fake");
1906 Maybe
<VideoDeviceEnumerationParams
> videoParams
;
1907 Maybe
<DeviceEnumerationParams
> audioParams
;
1908 nsAutoCString audioDev
;
1909 bool audioDevRead
= false;
1910 constexpr const char* VIDEO_DEV_NAME
= "media.video_loopback_dev";
1911 constexpr const char* AUDIO_DEV_NAME
= "media.audio_loopback_dev";
1912 const auto ensureDev
= [](const char* aPref
, nsAutoCString
* aLoopDev
,
1921 if (NS_FAILED(Preferences::GetCString(aPref
, *aLoopDev
))) {
1922 // Ensure we fall back to an empty string if reading the pref failed.
1923 aLoopDev
->SetIsVoid(true);
1926 if (MediaEngineSource::IsVideo(aVideoInputType
)) {
1927 nsAutoCString videoDev
;
1928 DeviceType type
= DeviceType::Real
;
1929 if (aVideoInputType
== MediaSourceEnum::Camera
) {
1930 // Fake and loopback devices are supported for only Camera.
1932 type
= DeviceType::Fake
;
1934 ensureDev(VIDEO_DEV_NAME
, &videoDev
, nullptr);
1935 // Loopback prefs take precedence over fake prefs
1936 if (fakeByPref
&& videoDev
.IsEmpty()) {
1937 type
= DeviceType::Fake
;
1939 // For groupId correlation we need the audio device name.
1940 ensureDev(AUDIO_DEV_NAME
, &audioDev
, &audioDevRead
);
1944 videoParams
= Some(VideoDeviceEnumerationParams(aVideoInputType
, type
,
1945 videoDev
, audioDev
));
1947 if (MediaEngineSource::IsAudio(aAudioInputType
)) {
1948 nsAutoCString realAudioDev
;
1949 DeviceType type
= DeviceType::Real
;
1950 if (aAudioInputType
== MediaSourceEnum::Microphone
) {
1951 // Fake and loopback devices are supported for only Microphone.
1953 type
= DeviceType::Fake
;
1955 ensureDev(AUDIO_DEV_NAME
, &audioDev
, &audioDevRead
);
1956 // Loopback prefs take precedence over fake prefs
1957 if (fakeByPref
&& audioDev
.IsEmpty()) {
1958 type
= DeviceType::Fake
;
1960 realAudioDev
= audioDev
;
1965 Some(DeviceEnumerationParams(aAudioInputType
, type
, realAudioDev
));
1967 return EnumerationParams(aFlags
, videoParams
, audioParams
);
1970 RefPtr
<DeviceSetPromise
>
1971 MediaManager::MaybeRequestPermissionAndEnumerateRawDevices(
1972 EnumerationParams aParams
) {
1973 MOZ_ASSERT(NS_IsMainThread());
1974 MOZ_ASSERT(aParams
.mVideo
.isSome() || aParams
.mAudio
.isSome() ||
1975 aParams
.mFlags
.contains(EnumerationFlag::EnumerateAudioOutputs
));
1977 LOG("%s: aVideoInputType=%" PRIu8
", aAudioInputType=%" PRIu8
, __func__
,
1978 static_cast<uint8_t>(aParams
.VideoInputType()),
1979 static_cast<uint8_t>(aParams
.AudioInputType()));
1981 if (sHasMainThreadShutdown
) {
1982 // The media thread is no longer available but the result will not be
1984 return DeviceSetPromise::CreateAndResolve(
1985 new MediaDeviceSetRefCnt(),
1986 "MaybeRequestPermissionAndEnumerateRawDevices: sync shutdown");
1989 const bool hasVideo
= aParams
.mVideo
.isSome();
1990 const bool hasAudio
= aParams
.mAudio
.isSome();
1991 const bool hasAudioOutput
=
1992 aParams
.mFlags
.contains(EnumerationFlag::EnumerateAudioOutputs
);
1993 const bool hasFakeCams
= aParams
.HasFakeCams();
1994 const bool hasFakeMics
= aParams
.HasFakeMics();
1995 // True if at least one of video input or audio input is a real device
1996 // or there is audio output.
1997 const bool realDeviceRequested
= (!hasFakeCams
&& hasVideo
) ||
1998 (!hasFakeMics
&& hasAudio
) || hasAudioOutput
;
2000 using NativePromise
=
2001 MozPromise
<CamerasAccessStatus
, mozilla::ipc::ResponseRejectReason
,
2002 /* IsExclusive = */ true>;
2003 RefPtr
<NativePromise
> deviceAccessPromise
;
2004 if (realDeviceRequested
&&
2005 aParams
.mFlags
.contains(EnumerationFlag::AllowPermissionRequest
) &&
2006 Preferences::GetBool("media.navigator.permission.device", false)) {
2007 // Need to ask permission to retrieve list of all devices;
2008 // notify frontend observer and wait for callback notification to post
2010 const char16_t
* const type
=
2011 (aParams
.VideoInputType() != MediaSourceEnum::Camera
) ? u
"audio"
2012 : (aParams
.AudioInputType() != MediaSourceEnum::Microphone
) ? u
"video"
2014 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
2015 DeviceAccessRequestPromiseHolderWithFallback deviceAccessPromiseHolder
;
2016 deviceAccessPromise
= deviceAccessPromiseHolder
.Ensure(__func__
);
2017 RefPtr task
= NS_NewRunnableFunction(
2018 __func__
, [holder
= std::move(deviceAccessPromiseHolder
)]() mutable {
2019 holder
.Resolve(CamerasAccessStatus::Granted
,
2020 "getUserMedia:got-device-permission");
2022 obs
->NotifyObservers(static_cast<nsIRunnable
*>(task
),
2023 "getUserMedia:ask-device-permission", type
);
2024 } else if (realDeviceRequested
&& hasVideo
&&
2025 aParams
.VideoInputType() == MediaSourceEnum::Camera
) {
2026 ipc::PBackgroundChild
* backgroundChild
=
2027 ipc::BackgroundChild::GetOrCreateForCurrentThread();
2028 deviceAccessPromise
= backgroundChild
->SendRequestCameraAccess(
2029 aParams
.mFlags
.contains(EnumerationFlag::AllowPermissionRequest
));
2032 if (!deviceAccessPromise
) {
2033 // No device access request needed. Proceed directly.
2034 deviceAccessPromise
=
2035 NativePromise::CreateAndResolve(CamerasAccessStatus::Granted
, __func__
);
2038 return deviceAccessPromise
->Then(
2039 GetCurrentSerialEventTarget(), __func__
,
2040 [this, self
= RefPtr(this), aParams
= std::move(aParams
)](
2041 NativePromise::ResolveOrRejectValue
&& aValue
) mutable {
2042 if (sHasMainThreadShutdown
) {
2043 return DeviceSetPromise::CreateAndResolve(
2044 new MediaDeviceSetRefCnt(),
2045 "MaybeRequestPermissionAndEnumerateRawDevices: async shutdown");
2048 if (aValue
.IsReject()) {
2049 // IPC failure probably means we're in shutdown. Resolve with
2050 // an empty set, so that callers do not need to handle rejection.
2051 return DeviceSetPromise::CreateAndResolve(
2052 new MediaDeviceSetRefCnt(),
2053 "MaybeRequestPermissionAndEnumerateRawDevices: ipc failure");
2056 if (auto v
= aValue
.ResolveValue();
2057 v
== CamerasAccessStatus::Error
||
2058 v
== CamerasAccessStatus::Rejected
) {
2059 LOG("Request to camera access %s",
2060 v
== CamerasAccessStatus::Rejected
? "was rejected" : "failed");
2061 if (v
== CamerasAccessStatus::Error
) {
2062 NS_WARNING("Failed to request camera access");
2064 return DeviceSetPromise::CreateAndReject(
2065 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
2066 "MaybeRequestPermissionAndEnumerateRawDevices: camera access "
2070 if (aParams
.mFlags
.contains(EnumerationFlag::AllowPermissionRequest
)) {
2071 MOZ_ASSERT(aValue
.ResolveValue() == CamerasAccessStatus::Granted
);
2072 EnsureNoPlaceholdersInDeviceCache();
2075 // We have to nest this, unfortunately, since we have no guarantees that
2076 // mMediaThread is alive. If we'd reject due to shutdown above, and have
2077 // the below async operation in a Then handler on the media thread the
2078 // Then handler would fail to dispatch and trip an assert on
2079 // destruction, for instance.
2081 mMediaThread
, __func__
, [aParams
= std::move(aParams
)]() mutable {
2082 return DeviceSetPromise::CreateAndResolve(
2083 EnumerateRawDevices(std::move(aParams
)),
2084 "MaybeRequestPermissionAndEnumerateRawDevices: success");
2090 * EnumerateRawDevices - Enumerate a list of audio & video devices that
2091 * satisfy passed-in constraints. List contains raw id's.
2094 /* static */ RefPtr
<MediaManager::MediaDeviceSetRefCnt
>
2095 MediaManager::EnumerateRawDevices(EnumerationParams aParams
) {
2096 MOZ_ASSERT(IsInMediaThread());
2097 // Only enumerate what's asked for, and only fake cams and mics.
2098 RefPtr
<MediaEngine
> fakeBackend
, realBackend
;
2099 if (aParams
.HasFakeCams() || aParams
.HasFakeMics()) {
2100 fakeBackend
= new MediaEngineFake();
2102 if (aParams
.RealDeviceRequested()) {
2103 MediaManager
* manager
= MediaManager::GetIfExists();
2104 MOZ_RELEASE_ASSERT(manager
, "Must exist while media thread is alive");
2105 realBackend
= manager
->GetBackend();
2108 RefPtr
<MediaEngine
> videoBackend
;
2109 RefPtr
<MediaEngine
> audioBackend
;
2110 Maybe
<MediaDeviceSet
> micsOfVideoBackend
;
2111 Maybe
<MediaDeviceSet
> speakers
;
2112 RefPtr devices
= new MediaDeviceSetRefCnt();
2114 // Enumerate microphones first, then cameras, then speakers, since
2115 // the enumerateDevices() algorithm expects them listed in that order.
2116 if (const auto& audio
= aParams
.mAudio
; audio
.isSome()) {
2117 audioBackend
= aParams
.HasFakeMics() ? fakeBackend
: realBackend
;
2118 MediaDeviceSet audios
;
2119 LOG("EnumerateRawDevices: Getting audio sources with %s backend",
2120 audioBackend
== fakeBackend
? "fake" : "real");
2121 GetMediaDevices(audioBackend
, audio
->mInputType
, audios
,
2122 audio
->mForcedDeviceName
.get());
2123 if (audio
->mInputType
== MediaSourceEnum::Microphone
&&
2124 audioBackend
== videoBackend
) {
2125 micsOfVideoBackend
.emplace();
2126 micsOfVideoBackend
->AppendElements(audios
);
2128 devices
->AppendElements(std::move(audios
));
2130 if (const auto& video
= aParams
.mVideo
; video
.isSome()) {
2131 videoBackend
= aParams
.HasFakeCams() ? fakeBackend
: realBackend
;
2132 MediaDeviceSet videos
;
2133 LOG("EnumerateRawDevices: Getting video sources with %s backend",
2134 videoBackend
== fakeBackend
? "fake" : "real");
2135 GetMediaDevices(videoBackend
, video
->mInputType
, videos
,
2136 video
->mForcedDeviceName
.get());
2137 devices
->AppendElements(std::move(videos
));
2139 if (aParams
.mFlags
.contains(EnumerationFlag::EnumerateAudioOutputs
)) {
2140 MediaDeviceSet outputs
;
2141 MOZ_ASSERT(realBackend
);
2142 realBackend
->EnumerateDevices(MediaSourceEnum::Other
,
2143 MediaSinkEnum::Speaker
, &outputs
);
2144 speakers
= Some(MediaDeviceSet());
2145 speakers
->AppendElements(outputs
);
2146 devices
->AppendElements(std::move(outputs
));
2148 if (aParams
.VideoInputType() == MediaSourceEnum::Camera
) {
2149 MediaDeviceSet audios
;
2150 LOG("EnumerateRawDevices: Getting audio sources with %s backend for "
2151 "groupId correlation",
2152 videoBackend
== fakeBackend
? "fake" : "real");
2153 // We need to correlate cameras with audio groupIds. We use the backend of
2154 // the camera to always do correlation on devices in the same scope. If we
2155 // don't do this, video-only getUserMedia will not apply groupId constraints
2156 // to the same set of groupIds as gets returned by enumerateDevices.
2157 if (micsOfVideoBackend
.isSome()) {
2158 // Microphones from the same backend used for the cameras have
2159 // already been enumerated. Avoid doing it again.
2160 MOZ_ASSERT(aParams
.mVideo
->mForcedMicrophoneName
==
2161 aParams
.mAudio
->mForcedDeviceName
);
2162 audios
.AppendElements(micsOfVideoBackend
.extract());
2164 GetMediaDevices(videoBackend
, MediaSourceEnum::Microphone
, audios
,
2165 aParams
.mVideo
->mForcedMicrophoneName
.get());
2167 if (videoBackend
== realBackend
) {
2168 // When using the real backend for video, there could also be
2169 // speakers to correlate with. There are no fake speakers.
2170 if (speakers
.isSome()) {
2171 // Speakers have already been enumerated. Avoid doing it again.
2172 audios
.AppendElements(speakers
.extract());
2174 realBackend
->EnumerateDevices(MediaSourceEnum::Other
,
2175 MediaSinkEnum::Speaker
, &audios
);
2178 GuessVideoDeviceGroupIDs(*devices
, audios
);
2184 RefPtr
<ConstDeviceSetPromise
> MediaManager::GetPhysicalDevices() {
2185 MOZ_ASSERT(NS_IsMainThread());
2186 if (mPhysicalDevices
) {
2187 return ConstDeviceSetPromise::CreateAndResolve(mPhysicalDevices
, __func__
);
2189 if (mPendingDevicesPromises
) {
2190 // Enumeration is already in progress.
2191 return mPendingDevicesPromises
->AppendElement()->Ensure(__func__
);
2193 mPendingDevicesPromises
=
2194 new Refcountable
<nsTArray
<MozPromiseHolder
<ConstDeviceSetPromise
>>>;
2195 MaybeRequestPermissionAndEnumerateRawDevices(
2196 CreateEnumerationParams(MediaSourceEnum::Camera
,
2197 MediaSourceEnum::Microphone
,
2198 EnumerationFlag::EnumerateAudioOutputs
))
2200 GetCurrentSerialEventTarget(), __func__
,
2201 [self
= RefPtr(this), this, promises
= mPendingDevicesPromises
](
2202 RefPtr
<MediaDeviceSetRefCnt
> aDevices
) mutable {
2203 for (auto& promiseHolder
: *promises
) {
2204 promiseHolder
.Resolve(aDevices
, __func__
);
2206 // mPendingDevicesPromises may have changed if devices have changed.
2207 if (promises
== mPendingDevicesPromises
) {
2208 mPendingDevicesPromises
= nullptr;
2209 mPhysicalDevices
= std::move(aDevices
);
2212 [](RefPtr
<MediaMgrError
>&& reason
) {
2213 MOZ_ASSERT_UNREACHABLE(
2214 "MaybeRequestPermissionAndEnumerateRawDevices does not reject");
2217 return mPendingDevicesPromises
->AppendElement()->Ensure(__func__
);
2220 MediaManager::MediaManager(already_AddRefed
<TaskQueue
> aMediaThread
)
2221 : mMediaThread(aMediaThread
), mBackend(nullptr) {
2222 mPrefs
.mFreq
= 1000; // 1KHz test tone
2223 mPrefs
.mWidth
= 0; // adaptive default
2224 mPrefs
.mHeight
= 0; // adaptive default
2225 mPrefs
.mFPS
= MediaEnginePrefs::DEFAULT_VIDEO_FPS
;
2226 mPrefs
.mAecOn
= false;
2227 mPrefs
.mUseAecMobile
= false;
2228 mPrefs
.mAgcOn
= false;
2229 mPrefs
.mHPFOn
= false;
2230 mPrefs
.mNoiseOn
= false;
2231 mPrefs
.mTransientOn
= false;
2232 mPrefs
.mAgc2Forced
= false;
2235 webrtc::AudioProcessing::Config::GainController1::Mode::kAdaptiveDigital
;
2237 webrtc::AudioProcessing::Config::NoiseSuppression::Level::kModerate
;
2242 mPrefs
.mChannels
= 0; // max channels default
2244 nsCOMPtr
<nsIPrefService
> prefs
=
2245 do_GetService("@mozilla.org/preferences-service;1", &rv
);
2246 if (NS_SUCCEEDED(rv
)) {
2247 nsCOMPtr
<nsIPrefBranch
> branch
= do_QueryInterface(prefs
);
2249 GetPrefs(branch
, nullptr);
2252 LOG("%s: default prefs: %dx%d @%dfps, %dHz test tones, aec: %s,"
2253 "agc: %s, hpf: %s, noise: %s, agc level: %d, agc version: %s, noise "
2254 "level: %d, transient: %s, channels %d",
2255 __FUNCTION__
, mPrefs
.mWidth
, mPrefs
.mHeight
, mPrefs
.mFPS
, mPrefs
.mFreq
,
2256 mPrefs
.mAecOn
? "on" : "off", mPrefs
.mAgcOn
? "on" : "off",
2257 mPrefs
.mHPFOn
? "on" : "off", mPrefs
.mNoiseOn
? "on" : "off", mPrefs
.mAgc
,
2258 mPrefs
.mAgc2Forced
? "2" : "1", mPrefs
.mNoise
,
2259 mPrefs
.mTransientOn
? "on" : "off", mPrefs
.mChannels
);
2262 NS_IMPL_ISUPPORTS(MediaManager
, nsIMediaManagerService
, nsIMemoryReporter
,
2266 StaticRefPtr
<MediaManager
> MediaManager::sSingleton
;
2270 bool MediaManager::IsInMediaThread() {
2271 return sSingleton
&& sSingleton
->mMediaThread
->IsOnCurrentThread();
2275 template <typename Function
>
2276 static void ForeachObservedPref(const Function
& aFunction
) {
2277 aFunction("media.navigator.video.default_width"_ns
);
2278 aFunction("media.navigator.video.default_height"_ns
);
2279 aFunction("media.navigator.video.default_fps"_ns
);
2280 aFunction("media.navigator.audio.fake_frequency"_ns
);
2281 aFunction("media.audio_loopback_dev"_ns
);
2282 aFunction("media.video_loopback_dev"_ns
);
2283 aFunction("media.getusermedia.fake-camera-name"_ns
);
2285 aFunction("media.getusermedia.aec_enabled"_ns
);
2286 aFunction("media.getusermedia.aec"_ns
);
2287 aFunction("media.getusermedia.agc_enabled"_ns
);
2288 aFunction("media.getusermedia.agc"_ns
);
2289 aFunction("media.getusermedia.hpf_enabled"_ns
);
2290 aFunction("media.getusermedia.noise_enabled"_ns
);
2291 aFunction("media.getusermedia.noise"_ns
);
2292 aFunction("media.getusermedia.channels"_ns
);
2293 aFunction("media.navigator.streams.fake"_ns
);
2297 // NOTE: never NS_DispatchAndSpinEventLoopUntilComplete to the MediaManager
2298 // thread from the MainThread, as we NS_DispatchAndSpinEventLoopUntilComplete to
2299 // MainThread from MediaManager thread.
2301 // Guaranteed never to return nullptr.
2303 MediaManager
* MediaManager::Get() {
2304 MOZ_ASSERT(NS_IsMainThread());
2307 static int timesCreated
= 0;
2309 MOZ_RELEASE_ASSERT(timesCreated
== 1);
2311 RefPtr
<TaskQueue
> mediaThread
= TaskQueue::Create(
2312 GetMediaThreadPool(MediaThreadType::SUPERVISOR
), "MediaManager");
2313 LOG("New Media thread for gum");
2315 sSingleton
= new MediaManager(mediaThread
.forget());
2317 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
2319 obs
->AddObserver(sSingleton
, "last-pb-context-exited", false);
2320 obs
->AddObserver(sSingleton
, "getUserMedia:got-device-permission", false);
2321 obs
->AddObserver(sSingleton
, "getUserMedia:privileged:allow", false);
2322 obs
->AddObserver(sSingleton
, "getUserMedia:response:allow", false);
2323 obs
->AddObserver(sSingleton
, "getUserMedia:response:deny", false);
2324 obs
->AddObserver(sSingleton
, "getUserMedia:response:noOSPermission",
2326 obs
->AddObserver(sSingleton
, "getUserMedia:revoke", false);
2327 obs
->AddObserver(sSingleton
, "getUserMedia:muteVideo", false);
2328 obs
->AddObserver(sSingleton
, "getUserMedia:unmuteVideo", false);
2329 obs
->AddObserver(sSingleton
, "getUserMedia:muteAudio", false);
2330 obs
->AddObserver(sSingleton
, "getUserMedia:unmuteAudio", false);
2331 obs
->AddObserver(sSingleton
, "application-background", false);
2332 obs
->AddObserver(sSingleton
, "application-foreground", false);
2334 // else MediaManager won't work properly and will leak (see bug 837874)
2335 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
2337 ForeachObservedPref([&](const nsLiteralCString
& aPrefName
) {
2338 prefs
->AddObserver(aPrefName
, sSingleton
, false);
2341 RegisterStrongMemoryReporter(sSingleton
);
2343 // Prepare async shutdown
2345 class Blocker
: public media::ShutdownBlocker
{
2348 : media::ShutdownBlocker(
2349 u
"Media shutdown: blocking on media thread"_ns
) {}
2351 NS_IMETHOD
BlockShutdown(nsIAsyncShutdownClient
*) override
{
2352 MOZ_RELEASE_ASSERT(MediaManager::GetIfExists());
2353 MediaManager::GetIfExists()->Shutdown();
2358 sSingleton
->mShutdownBlocker
= new Blocker();
2359 nsresult rv
= media::MustGetShutdownBarrier()->AddBlocker(
2360 sSingleton
->mShutdownBlocker
, NS_LITERAL_STRING_FROM_CSTRING(__FILE__
),
2362 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
));
2368 MediaManager
* MediaManager::GetIfExists() {
2369 MOZ_ASSERT(NS_IsMainThread() || IsInMediaThread());
2374 already_AddRefed
<MediaManager
> MediaManager::GetInstance() {
2375 // so we can have non-refcounted getters
2376 RefPtr
<MediaManager
> service
= MediaManager::Get();
2377 return service
.forget();
2380 media::Parent
<media::NonE10s
>* MediaManager::GetNonE10sParent() {
2381 if (!mNonE10sParent
) {
2382 mNonE10sParent
= new media::Parent
<media::NonE10s
>();
2384 return mNonE10sParent
;
2388 void MediaManager::Dispatch(already_AddRefed
<Runnable
> task
) {
2389 MOZ_ASSERT(NS_IsMainThread());
2390 if (sHasMainThreadShutdown
) {
2391 // Can't safely delete task here since it may have items with specific
2392 // thread-release requirements.
2393 // XXXkhuey well then who is supposed to delete it?! We don't signal
2394 // that we failed ...
2398 NS_ASSERTION(Get(), "MediaManager singleton?");
2399 NS_ASSERTION(Get()->mMediaThread
, "No thread yet");
2400 MOZ_ALWAYS_SUCCEEDS(Get()->mMediaThread
->Dispatch(std::move(task
)));
2403 template <typename MozPromiseType
, typename FunctionType
>
2405 RefPtr
<MozPromiseType
> MediaManager::Dispatch(const char* aName
,
2406 FunctionType
&& aFunction
) {
2407 MozPromiseHolder
<MozPromiseType
> holder
;
2408 RefPtr
<MozPromiseType
> promise
= holder
.Ensure(aName
);
2409 MediaManager::Dispatch(NS_NewRunnableFunction(
2410 aName
, [h
= std::move(holder
), func
= std::forward
<FunctionType
>(
2411 aFunction
)]() mutable { func(h
); }));
2416 nsresult
MediaManager::NotifyRecordingStatusChange(
2417 nsPIDOMWindowInner
* aWindow
) {
2418 NS_ENSURE_ARG(aWindow
);
2420 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
2423 "Could not get the Observer service for GetUserMedia recording "
2425 return NS_ERROR_FAILURE
;
2428 auto props
= MakeRefPtr
<nsHashPropertyBag
>();
2431 nsCOMPtr
<nsIURI
> docURI
= aWindow
->GetDocumentURI();
2432 NS_ENSURE_TRUE(docURI
, NS_ERROR_FAILURE
);
2434 nsresult rv
= docURI
->GetSpec(pageURL
);
2435 NS_ENSURE_SUCCESS(rv
, rv
);
2437 NS_ConvertUTF8toUTF16
requestURL(pageURL
);
2439 props
->SetPropertyAsAString(u
"requestURL"_ns
, requestURL
);
2440 props
->SetPropertyAsInterface(u
"window"_ns
, aWindow
);
2442 obs
->NotifyObservers(static_cast<nsIPropertyBag2
*>(props
),
2443 "recording-device-events", nullptr);
2444 LOG("Sent recording-device-events for url '%s'", pageURL
.get());
2449 void MediaManager::DeviceListChanged() {
2450 MOZ_ASSERT(NS_IsMainThread());
2451 if (sHasMainThreadShutdown
) {
2454 // Invalidate immediately to provide an up-to-date device list for future
2455 // enumerations on platforms with sane device-list-changed events.
2456 InvalidateDeviceCache();
2458 // Wait 200 ms, because
2459 // A) on some Windows machines, if we call EnumerateRawDevices immediately
2460 // after receiving devicechange event, we'd get an outdated devices list.
2461 // B) Waiting helps coalesce multiple calls on us into one, which can happen
2462 // if a device with both audio input and output is attached or removed.
2463 // We want to react & fire a devicechange event only once in that case.
2465 // The wait is extended if another hardware device-list-changed notification
2466 // is received to provide the full 200ms for EnumerateRawDevices().
2467 if (mDeviceChangeTimer
) {
2468 mDeviceChangeTimer
->Cancel();
2470 mDeviceChangeTimer
= MakeRefPtr
<MediaTimer
>();
2472 // However, if this would cause a delay of over 1000ms in handling the
2473 // oldest unhandled event, then respond now and set the timer to run
2474 // EnumerateRawDevices() again in 200ms.
2475 auto now
= TimeStamp::NowLoRes();
2476 auto enumerateDelay
= TimeDuration::FromMilliseconds(200);
2477 auto coalescenceLimit
= TimeDuration::FromMilliseconds(1000) - enumerateDelay
;
2478 if (!mUnhandledDeviceChangeTime
) {
2479 mUnhandledDeviceChangeTime
= now
;
2480 } else if (now
- mUnhandledDeviceChangeTime
> coalescenceLimit
) {
2481 HandleDeviceListChanged();
2482 mUnhandledDeviceChangeTime
= now
;
2484 RefPtr
<MediaManager
> self
= this;
2485 mDeviceChangeTimer
->WaitFor(enumerateDelay
, __func__
)
2487 GetCurrentSerialEventTarget(), __func__
,
2489 // Invalidate again for the sake of platforms with inconsistent
2490 // timing between device-list-changed notification and enumeration.
2491 InvalidateDeviceCache();
2493 mUnhandledDeviceChangeTime
= TimeStamp();
2494 HandleDeviceListChanged();
2496 [] { /* Timer was canceled by us, or we're in shutdown. */ });
2499 void MediaManager::EnsureNoPlaceholdersInDeviceCache() {
2500 MOZ_ASSERT(NS_IsMainThread());
2502 if (mPhysicalDevices
) {
2503 // Invalidate the list if there is a placeholder
2504 for (const auto& device
: *mPhysicalDevices
) {
2505 if (device
->mIsPlaceholder
) {
2506 InvalidateDeviceCache();
2513 void MediaManager::InvalidateDeviceCache() {
2514 MOZ_ASSERT(NS_IsMainThread());
2516 mPhysicalDevices
= nullptr;
2517 // Disconnect any in-progress enumeration, which may now be out of date,
2518 // from updating mPhysicalDevices or resolving future device request
2520 mPendingDevicesPromises
= nullptr;
2523 void MediaManager::HandleDeviceListChanged() {
2524 mDeviceListChangeEvent
.Notify();
2526 GetPhysicalDevices()->Then(
2527 GetCurrentSerialEventTarget(), __func__
,
2528 [self
= RefPtr(this), this](RefPtr
<const MediaDeviceSetRefCnt
> aDevices
) {
2529 if (!MediaManager::GetIfExists()) {
2533 nsTHashSet
<nsString
> deviceIDs
;
2534 for (const auto& device
: *aDevices
) {
2535 deviceIDs
.Insert(device
->mRawID
);
2537 // For any real removed cameras or microphones, notify their
2538 // listeners cleanly that the source has stopped, so JS knows and
2539 // usage indicators update.
2540 // First collect the listeners in an array to stop them after
2541 // iterating the hashtable. The StopRawID() method indirectly
2542 // modifies the mActiveWindows and would assert-crash if the
2543 // iterator were active while the table is being enumerated.
2544 const auto windowListeners
= ToArray(mActiveWindows
.Values());
2545 for (const RefPtr
<GetUserMediaWindowListener
>& l
: windowListeners
) {
2546 const auto activeDevices
= l
->GetDevices();
2547 for (const RefPtr
<LocalMediaDevice
>& device
: *activeDevices
) {
2548 if (device
->IsFake()) {
2551 MediaSourceEnum mediaSource
= device
->GetMediaSource();
2552 if (mediaSource
!= MediaSourceEnum::Microphone
&&
2553 mediaSource
!= MediaSourceEnum::Camera
) {
2556 if (!deviceIDs
.Contains(device
->RawID())) {
2557 // Device has been removed
2558 l
->StopRawID(device
->RawID());
2563 [](RefPtr
<MediaMgrError
>&& reason
) {
2564 MOZ_ASSERT_UNREACHABLE("EnumerateRawDevices does not reject");
2568 size_t MediaManager::AddTaskAndGetCount(uint64_t aWindowID
,
2569 const nsAString
& aCallID
,
2570 RefPtr
<GetUserMediaTask
> aTask
) {
2571 // Store the task w/callbacks.
2572 mActiveCallbacks
.InsertOrUpdate(aCallID
, std::move(aTask
));
2574 // Add a WindowID cross-reference so OnNavigation can tear things down
2575 nsTArray
<nsString
>* const array
= mCallIds
.GetOrInsertNew(aWindowID
);
2576 array
->AppendElement(aCallID
);
2578 return array
->Length();
2581 RefPtr
<GetUserMediaTask
> MediaManager::TakeGetUserMediaTask(
2582 const nsAString
& aCallID
) {
2583 RefPtr
<GetUserMediaTask
> task
;
2584 mActiveCallbacks
.Remove(aCallID
, getter_AddRefs(task
));
2588 nsTArray
<nsString
>* array
;
2589 mCallIds
.Get(task
->GetWindowID(), &array
);
2591 array
->RemoveElement(aCallID
);
2595 void MediaManager::NotifyAllowed(const nsString
& aCallID
,
2596 const LocalMediaDeviceSet
& aDevices
) {
2597 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
2598 nsCOMPtr
<nsIMutableArray
> devicesCopy
= nsArray::Create();
2599 for (const auto& device
: aDevices
) {
2600 nsresult rv
= devicesCopy
->AppendElement(device
);
2601 if (NS_WARN_IF(NS_FAILED(rv
))) {
2602 obs
->NotifyObservers(nullptr, "getUserMedia:response:deny",
2607 obs
->NotifyObservers(devicesCopy
, "getUserMedia:privileged:allow",
2611 nsresult
MediaManager::GenerateUUID(nsAString
& aResult
) {
2613 nsCOMPtr
<nsIUUIDGenerator
> uuidgen
=
2614 do_GetService("@mozilla.org/uuid-generator;1", &rv
);
2615 NS_ENSURE_SUCCESS(rv
, rv
);
2617 // Generate a call ID.
2619 rv
= uuidgen
->GenerateUUIDInPlace(&id
);
2620 NS_ENSURE_SUCCESS(rv
, rv
);
2622 char buffer
[NSID_LENGTH
];
2623 id
.ToProvidedString(buffer
);
2624 aResult
.Assign(NS_ConvertUTF8toUTF16(buffer
));
2628 enum class GetUserMediaSecurityState
{
2639 * This function is used in getUserMedia when privacy.resistFingerprinting is
2640 * true. Only mediaSource of audio/video constraint will be kept.
2642 static void ReduceConstraint(
2643 OwningBooleanOrMediaTrackConstraints
& aConstraint
) {
2644 // Not requesting stream.
2645 if (!MediaManager::IsOn(aConstraint
)) {
2649 // It looks like {audio: true}, do nothing.
2650 if (!aConstraint
.IsMediaTrackConstraints()) {
2654 // Keep mediaSource, ignore all other constraints.
2655 Maybe
<nsString
> mediaSource
;
2656 if (aConstraint
.GetAsMediaTrackConstraints().mMediaSource
.WasPassed()) {
2658 Some(aConstraint
.GetAsMediaTrackConstraints().mMediaSource
.Value());
2660 aConstraint
.Uninit();
2662 aConstraint
.SetAsMediaTrackConstraints().mMediaSource
.Construct(
2665 Unused
<< aConstraint
.SetAsMediaTrackConstraints();
2670 * The entry point for this file. A call from Navigator::mozGetUserMedia
2671 * will end up here. MediaManager is a singleton that is responsible
2672 * for handling all incoming getUserMedia calls from every window.
2674 RefPtr
<MediaManager::StreamPromise
> MediaManager::GetUserMedia(
2675 nsPIDOMWindowInner
* aWindow
,
2676 const MediaStreamConstraints
& aConstraintsPassedIn
,
2677 CallerType aCallerType
) {
2678 MOZ_ASSERT(NS_IsMainThread());
2679 MOZ_ASSERT(aWindow
);
2680 uint64_t windowID
= aWindow
->WindowID();
2682 MediaStreamConstraints
c(aConstraintsPassedIn
); // use a modifiable copy
2684 if (sHasMainThreadShutdown
) {
2685 return StreamPromise::CreateAndReject(
2686 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
,
2691 // Determine permissions early (while we still have a stack).
2693 nsIURI
* docURI
= aWindow
->GetDocumentURI();
2695 return StreamPromise::CreateAndReject(
2696 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
), __func__
);
2698 bool isChrome
= (aCallerType
== CallerType::System
);
2701 Preferences::GetBool("media.navigator.permission.disabled", false);
2702 bool isSecure
= aWindow
->IsSecureContext();
2703 bool isHandlingUserInput
= UserActivation::IsHandlingUserInput();
2705 nsresult rv
= docURI
->GetHost(host
);
2707 nsCOMPtr
<nsIPrincipal
> principal
=
2708 nsGlobalWindowInner::Cast(aWindow
)->GetPrincipal();
2709 if (NS_WARN_IF(!principal
)) {
2710 return StreamPromise::CreateAndReject(
2711 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::SecurityError
),
2715 Document
* doc
= aWindow
->GetExtantDoc();
2716 if (NS_WARN_IF(!doc
)) {
2717 return StreamPromise::CreateAndReject(
2718 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::SecurityError
),
2722 // Disallow access to null principal pages and http pages (unless pref)
2723 if (principal
->GetIsNullPrincipal() ||
2724 !(isSecure
|| StaticPrefs::media_getusermedia_insecure_enabled())) {
2725 return StreamPromise::CreateAndReject(
2726 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
2730 // This principal needs to be sent to different threads and so via IPC.
2731 // For this reason it's better to convert it to PrincipalInfo right now.
2732 ipc::PrincipalInfo principalInfo
;
2733 rv
= PrincipalToPrincipalInfo(principal
, &principalInfo
);
2734 if (NS_WARN_IF(NS_FAILED(rv
))) {
2735 return StreamPromise::CreateAndReject(
2736 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::SecurityError
),
2740 const bool resistFingerprinting
=
2741 !isChrome
&& doc
->ShouldResistFingerprinting(RFPTarget::MediaDevices
);
2742 if (resistFingerprinting
) {
2743 ReduceConstraint(c
.mVideo
);
2744 ReduceConstraint(c
.mAudio
);
2747 if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
2748 c
.mVideo
.SetAsBoolean() = false;
2751 MediaSourceEnum videoType
= MediaSourceEnum::Other
; // none
2752 MediaSourceEnum audioType
= MediaSourceEnum::Other
; // none
2754 if (c
.mVideo
.IsMediaTrackConstraints()) {
2755 auto& vc
= c
.mVideo
.GetAsMediaTrackConstraints();
2756 if (!vc
.mMediaSource
.WasPassed()) {
2757 vc
.mMediaSource
.Construct().AssignASCII(
2758 dom::MediaSourceEnumValues::GetString(MediaSourceEnum::Camera
));
2760 videoType
= StringToEnum(dom::MediaSourceEnumValues::strings
,
2761 vc
.mMediaSource
.Value(), MediaSourceEnum::Other
);
2762 Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE
,
2763 (uint32_t)videoType
);
2764 switch (videoType
) {
2765 case MediaSourceEnum::Camera
:
2768 case MediaSourceEnum::Browser
:
2769 // If no window id is passed in then default to the caller's window.
2770 // Functional defaults are helpful in tests, but also a natural outcome
2771 // of the constraints API's limited semantics for requiring input.
2772 if (!vc
.mBrowserWindow
.WasPassed()) {
2773 nsPIDOMWindowOuter
* outer
= aWindow
->GetOuterWindow();
2774 vc
.mBrowserWindow
.Construct(outer
->WindowID());
2777 case MediaSourceEnum::Screen
:
2778 case MediaSourceEnum::Window
:
2779 // Deny screensharing request if support is disabled, or
2780 // the requesting document is not from a host on the whitelist.
2781 if (!Preferences::GetBool(
2782 ((videoType
== MediaSourceEnum::Browser
)
2783 ? "media.getusermedia.browser.enabled"
2784 : "media.getusermedia.screensharing.enabled"),
2786 (!privileged
&& !aWindow
->IsSecureContext())) {
2787 return StreamPromise::CreateAndReject(
2788 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
2793 case MediaSourceEnum::Microphone
:
2794 case MediaSourceEnum::Other
:
2796 return StreamPromise::CreateAndReject(
2797 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::OverconstrainedError
,
2798 "", u
"mediaSource"_ns
),
2804 // Only allow privileged content to explicitly pick full-screen,
2805 // application or tabsharing, since these modes are still available for
2806 // testing. All others get "Window" (*) sharing.
2808 // *) We overload "Window" with the new default getDisplayMedia spec-
2809 // mandated behavior of not influencing user-choice, which we currently
2810 // implement as a list containing BOTH windows AND screen(s).
2812 // Notes on why we chose "Window" as the one to overload. Two reasons:
2814 // 1. It's the closest logically & behaviorally (multi-choice, no default)
2815 // 2. Screen is still useful in tests (implicit default is entire screen)
2817 // For UX reasons we don't want "Entire Screen" to be the first/default
2818 // choice (in our code first=default). It's a "scary" source that comes
2819 // with complicated warnings on-top that would be confusing as the first
2820 // thing people see, and also deserves to be listed as last resort for
2823 if (videoType
== MediaSourceEnum::Screen
||
2824 videoType
== MediaSourceEnum::Browser
) {
2825 videoType
= MediaSourceEnum::Window
;
2826 vc
.mMediaSource
.Value().AssignASCII(
2827 dom::MediaSourceEnumValues::GetString(videoType
));
2829 // only allow privileged content to set the window id
2830 if (vc
.mBrowserWindow
.WasPassed()) {
2831 vc
.mBrowserWindow
.Value() = -1;
2833 if (vc
.mAdvanced
.WasPassed()) {
2834 for (MediaTrackConstraintSet
& cs
: vc
.mAdvanced
.Value()) {
2835 if (cs
.mBrowserWindow
.WasPassed()) {
2836 cs
.mBrowserWindow
.Value() = -1;
2841 } else if (IsOn(c
.mVideo
)) {
2842 videoType
= MediaSourceEnum::Camera
;
2843 Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE
,
2844 (uint32_t)videoType
);
2847 if (c
.mAudio
.IsMediaTrackConstraints()) {
2848 auto& ac
= c
.mAudio
.GetAsMediaTrackConstraints();
2849 if (!ac
.mMediaSource
.WasPassed()) {
2850 ac
.mMediaSource
.Construct(NS_ConvertASCIItoUTF16(
2851 dom::MediaSourceEnumValues::GetString(MediaSourceEnum::Microphone
)));
2853 audioType
= StringToEnum(dom::MediaSourceEnumValues::strings
,
2854 ac
.mMediaSource
.Value(), MediaSourceEnum::Other
);
2855 Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE
,
2856 (uint32_t)audioType
);
2858 switch (audioType
) {
2859 case MediaSourceEnum::Microphone
:
2862 case MediaSourceEnum::AudioCapture
:
2863 // Only enable AudioCapture if the pref is enabled. If it's not, we can
2865 if (!Preferences::GetBool("media.getusermedia.audiocapture.enabled")) {
2866 return StreamPromise::CreateAndReject(
2867 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
2872 case MediaSourceEnum::Other
:
2874 return StreamPromise::CreateAndReject(
2875 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::OverconstrainedError
,
2876 "", u
"mediaSource"_ns
),
2880 } else if (IsOn(c
.mAudio
)) {
2881 audioType
= MediaSourceEnum::Microphone
;
2882 Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE
,
2883 (uint32_t)audioType
);
2886 // Create a window listener if it doesn't already exist.
2887 RefPtr
<GetUserMediaWindowListener
> windowListener
=
2888 GetOrMakeWindowListener(aWindow
);
2889 MOZ_ASSERT(windowListener
);
2890 // Create an inactive DeviceListener to act as a placeholder, so the
2891 // window listener doesn't clean itself up until we're done.
2892 auto placeholderListener
= MakeRefPtr
<DeviceListener
>();
2893 windowListener
->Register(placeholderListener
);
2895 { // Check Permissions Policy. Reject if a requested feature is disabled.
2896 bool disabled
= !IsOn(c
.mAudio
) && !IsOn(c
.mVideo
);
2897 if (IsOn(c
.mAudio
)) {
2898 if (audioType
== MediaSourceEnum::Microphone
) {
2899 if (Preferences::GetBool("media.getusermedia.microphone.deny", false) ||
2900 !FeaturePolicyUtils::IsFeatureAllowed(doc
, u
"microphone"_ns
)) {
2903 } else if (!FeaturePolicyUtils::IsFeatureAllowed(doc
,
2904 u
"display-capture"_ns
)) {
2908 if (IsOn(c
.mVideo
)) {
2909 if (videoType
== MediaSourceEnum::Camera
) {
2910 if (Preferences::GetBool("media.getusermedia.camera.deny", false) ||
2911 !FeaturePolicyUtils::IsFeatureAllowed(doc
, u
"camera"_ns
)) {
2914 } else if (!FeaturePolicyUtils::IsFeatureAllowed(doc
,
2915 u
"display-capture"_ns
)) {
2921 placeholderListener
->Stop();
2922 return StreamPromise::CreateAndReject(
2923 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
2928 // Get list of all devices, with origin-specific device ids.
2930 MediaEnginePrefs prefs
= mPrefs
;
2933 rv
= GenerateUUID(callID
);
2934 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
));
2936 bool hasVideo
= videoType
!= MediaSourceEnum::Other
;
2937 bool hasAudio
= audioType
!= MediaSourceEnum::Other
;
2939 // Handle fake requests from content. For gUM we don't consider resist
2940 // fingerprinting as users should be prompted anyway.
2941 bool forceFakes
= c
.mFake
.WasPassed() && c
.mFake
.Value();
2942 // fake:true is effective only for microphone and camera devices, so
2943 // permission must be requested for screen capture even if fake:true is set.
2944 bool hasOnlyForcedFakes
=
2945 forceFakes
&& (!hasVideo
|| videoType
== MediaSourceEnum::Camera
) &&
2946 (!hasAudio
|| audioType
== MediaSourceEnum::Microphone
);
2947 bool askPermission
=
2949 Preferences::GetBool("media.navigator.permission.force")) &&
2950 (!hasOnlyForcedFakes
||
2951 Preferences::GetBool("media.navigator.permission.fake"));
2953 LOG("%s: Preparing to enumerate devices. windowId=%" PRIu64
2954 ", videoType=%" PRIu8
", audioType=%" PRIu8
2955 ", forceFakes=%s, askPermission=%s",
2956 __func__
, windowID
, static_cast<uint8_t>(videoType
),
2957 static_cast<uint8_t>(audioType
), forceFakes
? "true" : "false",
2958 askPermission
? "true" : "false");
2960 EnumerationFlags flags
= EnumerationFlag::AllowPermissionRequest
;
2962 flags
+= EnumerationFlag::ForceFakes
;
2964 RefPtr
<MediaManager
> self
= this;
2965 return EnumerateDevicesImpl(
2966 aWindow
, CreateEnumerationParams(videoType
, audioType
, flags
))
2968 GetCurrentSerialEventTarget(), __func__
,
2969 [self
, windowID
, c
, windowListener
,
2970 aCallerType
](RefPtr
<LocalMediaDeviceSetRefCnt
> aDevices
) {
2971 LOG("GetUserMedia: post enumeration promise success callback "
2973 // Ensure that our windowID is still good.
2974 RefPtr
<nsPIDOMWindowInner
> window
=
2975 nsGlobalWindowInner::GetInnerWindowWithId(windowID
);
2976 if (!window
|| !self
->IsWindowListenerStillActive(windowListener
)) {
2977 LOG("GetUserMedia: bad window (%" PRIu64
2978 ") in post enumeration success callback!",
2980 return LocalDeviceSetPromise::CreateAndReject(
2981 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
),
2984 // Apply any constraints. This modifies the passed-in list.
2985 return self
->SelectSettings(c
, aCallerType
, std::move(aDevices
));
2987 [](RefPtr
<MediaMgrError
>&& aError
) {
2988 LOG("GetUserMedia: post enumeration EnumerateDevicesImpl "
2989 "failure callback called!");
2990 return LocalDeviceSetPromise::CreateAndReject(std::move(aError
),
2994 GetCurrentSerialEventTarget(), __func__
,
2995 [self
, windowID
, c
, windowListener
, placeholderListener
, hasAudio
,
2996 hasVideo
, askPermission
, prefs
, isSecure
, isHandlingUserInput
,
2997 callID
, principalInfo
, aCallerType
, resistFingerprinting
](
2998 RefPtr
<LocalMediaDeviceSetRefCnt
> aDevices
) mutable {
2999 LOG("GetUserMedia: starting post enumeration promise2 success "
3002 // Ensure that the window is still good.
3003 RefPtr
<nsPIDOMWindowInner
> window
=
3004 nsGlobalWindowInner::GetInnerWindowWithId(windowID
);
3005 if (!window
|| !self
->IsWindowListenerStillActive(windowListener
)) {
3006 LOG("GetUserMedia: bad window (%" PRIu64
3007 ") in post enumeration success callback 2!",
3009 placeholderListener
->Stop();
3010 return StreamPromise::CreateAndReject(
3011 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
),
3014 if (!aDevices
->Length()) {
3015 LOG("GetUserMedia: no devices found in post enumeration promise2 "
3016 "success callback! Calling error handler!");
3017 placeholderListener
->Stop();
3018 // When privacy.resistFingerprinting = true, no
3019 // available device implies content script is requesting
3020 // a fake device, so report NotAllowedError.
3021 auto error
= resistFingerprinting
3022 ? MediaMgrError::Name::NotAllowedError
3023 : MediaMgrError::Name::NotFoundError
;
3024 return StreamPromise::CreateAndReject(
3025 MakeRefPtr
<MediaMgrError
>(error
), __func__
);
3028 // Time to start devices. Create the necessary device listeners and
3029 // remove the placeholder.
3030 RefPtr
<DeviceListener
> audioListener
;
3031 RefPtr
<DeviceListener
> videoListener
;
3033 audioListener
= MakeRefPtr
<DeviceListener
>();
3034 windowListener
->Register(audioListener
);
3037 videoListener
= MakeRefPtr
<DeviceListener
>();
3038 windowListener
->Register(videoListener
);
3040 placeholderListener
->Stop();
3042 bool focusSource
= mozilla::Preferences::GetBool(
3043 "media.getusermedia.window.focus_source.enabled", true);
3045 // Incremental hack to compile. To be replaced by deeper
3046 // refactoring. MediaManager allows
3047 // "neither-resolve-nor-reject" semantics, so we cannot
3048 // use MozPromiseHolder here.
3049 MozPromiseHolder
<StreamPromise
> holder
;
3050 RefPtr
<StreamPromise
> p
= holder
.Ensure(__func__
);
3052 // Pass callbacks and listeners along to GetUserMediaStreamTask.
3053 auto task
= MakeRefPtr
<GetUserMediaStreamTask
>(
3054 c
, std::move(holder
), windowID
, std::move(windowListener
),
3055 std::move(audioListener
), std::move(videoListener
), prefs
,
3056 principalInfo
, aCallerType
, focusSource
);
3059 self
->AddTaskAndGetCount(windowID
, callID
, std::move(task
));
3061 if (!askPermission
) {
3062 self
->NotifyAllowed(callID
, *aDevices
);
3064 auto req
= MakeRefPtr
<GetUserMediaRequest
>(
3065 window
, callID
, std::move(aDevices
), c
, isSecure
,
3066 isHandlingUserInput
);
3067 if (!Preferences::GetBool("media.navigator.permission.force") &&
3069 // there is at least 1 pending gUM request
3070 // For the scarySources test case, always send the
3072 self
->mPendingGUMRequest
.AppendElement(req
.forget());
3074 nsCOMPtr
<nsIObserverService
> obs
=
3075 services::GetObserverService();
3076 obs
->NotifyObservers(req
, "getUserMedia:request", nullptr);
3080 self
->mLogHandle
= EnsureWebrtcLogging();
3084 [placeholderListener
](RefPtr
<MediaMgrError
>&& aError
) {
3085 LOG("GetUserMedia: post enumeration SelectSettings failure "
3086 "callback called!");
3087 placeholderListener
->Stop();
3088 return StreamPromise::CreateAndReject(std::move(aError
), __func__
);
3092 RefPtr
<LocalDeviceSetPromise
> MediaManager::AnonymizeDevices(
3093 nsPIDOMWindowInner
* aWindow
, RefPtr
<const MediaDeviceSetRefCnt
> aDevices
) {
3094 // Get an origin-key (for either regular or private browsing).
3095 MOZ_ASSERT(NS_IsMainThread());
3096 uint64_t windowId
= aWindow
->WindowID();
3097 nsCOMPtr
<nsIPrincipal
> principal
=
3098 nsGlobalWindowInner::Cast(aWindow
)->GetPrincipal();
3099 MOZ_ASSERT(principal
);
3100 ipc::PrincipalInfo principalInfo
;
3101 nsresult rv
= PrincipalToPrincipalInfo(principal
, &principalInfo
);
3102 if (NS_WARN_IF(NS_FAILED(rv
))) {
3103 return LocalDeviceSetPromise::CreateAndReject(
3104 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
3107 bool persist
= IsActivelyCapturingOrHasAPermission(windowId
);
3108 return media::GetPrincipalKey(principalInfo
, persist
)
3110 GetMainThreadSerialEventTarget(), __func__
,
3111 [rawDevices
= std::move(aDevices
),
3112 windowId
](const nsCString
& aOriginKey
) {
3113 MOZ_ASSERT(!aOriginKey
.IsEmpty());
3114 RefPtr anonymized
= new LocalMediaDeviceSetRefCnt();
3115 for (const RefPtr
<MediaDevice
>& device
: *rawDevices
) {
3116 nsString id
= device
->mRawID
;
3117 // An empty id represents a virtual default device, for which
3118 // the exposed deviceId is the empty string.
3119 if (!id
.IsEmpty()) {
3120 nsContentUtils::AnonymizeId(id
, aOriginKey
);
3122 nsString groupId
= device
->mRawGroupID
;
3123 // Use window id to salt group id in order to make it session
3124 // based as required by the spec. This does not provide unique
3125 // group ids through out a browser restart. However, this is not
3126 // against the spec. Furthermore, since device ids are the same
3127 // after a browser restart the fingerprint is not bigger.
3128 groupId
.AppendInt(windowId
);
3129 nsContentUtils::AnonymizeId(groupId
, aOriginKey
);
3131 nsString name
= device
->mRawName
;
3132 if (name
.Find(u
"AirPods"_ns
) != -1) {
3133 name
= u
"AirPods"_ns
;
3135 anonymized
->EmplaceBack(
3136 new LocalMediaDevice(device
, id
, groupId
, name
));
3138 return LocalDeviceSetPromise::CreateAndResolve(anonymized
,
3142 NS_WARNING("AnonymizeDevices failed to get Principal Key");
3143 return LocalDeviceSetPromise::CreateAndReject(
3144 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
),
3149 RefPtr
<LocalDeviceSetPromise
> MediaManager::EnumerateDevicesImpl(
3150 nsPIDOMWindowInner
* aWindow
, EnumerationParams aParams
) {
3151 MOZ_ASSERT(NS_IsMainThread());
3153 uint64_t windowId
= aWindow
->WindowID();
3154 LOG("%s: windowId=%" PRIu64
", aVideoInputType=%" PRIu8
3155 ", aAudioInputType=%" PRIu8
,
3156 __func__
, windowId
, static_cast<uint8_t>(aParams
.VideoInputType()),
3157 static_cast<uint8_t>(aParams
.AudioInputType()));
3159 // To get a device list anonymized for a particular origin, we must:
3160 // 1. Get the raw devices list
3161 // 2. Anonymize the raw list with an origin-key.
3163 // Add the window id here to check for that and abort silently if no longer
3165 RefPtr
<GetUserMediaWindowListener
> windowListener
=
3166 GetOrMakeWindowListener(aWindow
);
3167 MOZ_ASSERT(windowListener
);
3168 // Create an inactive DeviceListener to act as a placeholder, so the
3169 // window listener doesn't clean itself up until we're done.
3170 auto placeholderListener
= MakeRefPtr
<DeviceListener
>();
3171 windowListener
->Register(placeholderListener
);
3173 return MaybeRequestPermissionAndEnumerateRawDevices(std::move(aParams
))
3175 GetMainThreadSerialEventTarget(), __func__
,
3176 [self
= RefPtr(this), this, window
= nsCOMPtr(aWindow
),
3177 placeholderListener
](RefPtr
<MediaDeviceSetRefCnt
> aDevices
) mutable {
3178 // Only run if window is still on our active list.
3179 MediaManager
* mgr
= MediaManager::GetIfExists();
3180 if (!mgr
|| placeholderListener
->Stopped()) {
3181 // The listener has already been removed if the window is no
3183 return LocalDeviceSetPromise::CreateAndReject(
3184 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
),
3187 MOZ_ASSERT(mgr
->IsWindowStillActive(window
->WindowID()));
3188 placeholderListener
->Stop();
3189 return AnonymizeDevices(window
, aDevices
);
3191 [placeholderListener
](RefPtr
<MediaMgrError
>&& aError
) {
3192 // EnumerateDevicesImpl may fail if a new doc has been set, in which
3193 // case the OnNavigation() method should have removed all previous
3194 // active listeners, or if a platform device access request was not
3196 placeholderListener
->Stop();
3197 return LocalDeviceSetPromise::CreateAndReject(std::move(aError
),
3202 RefPtr
<LocalDevicePromise
> MediaManager::SelectAudioOutput(
3203 nsPIDOMWindowInner
* aWindow
, const dom::AudioOutputOptions
& aOptions
,
3204 CallerType aCallerType
) {
3205 bool isHandlingUserInput
= UserActivation::IsHandlingUserInput();
3206 nsCOMPtr
<nsIPrincipal
> principal
=
3207 nsGlobalWindowInner::Cast(aWindow
)->GetPrincipal();
3208 if (!FeaturePolicyUtils::IsFeatureAllowed(aWindow
->GetExtantDoc(),
3209 u
"speaker-selection"_ns
)) {
3210 return LocalDevicePromise::CreateAndReject(
3211 MakeRefPtr
<MediaMgrError
>(
3212 MediaMgrError::Name::NotAllowedError
,
3213 "Document's Permissions Policy does not allow selectAudioOutput()"),
3216 if (NS_WARN_IF(!principal
)) {
3217 return LocalDevicePromise::CreateAndReject(
3218 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::SecurityError
),
3221 // Disallow access to null principal.
3222 if (principal
->GetIsNullPrincipal()) {
3223 return LocalDevicePromise::CreateAndReject(
3224 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::NotAllowedError
),
3227 ipc::PrincipalInfo principalInfo
;
3228 nsresult rv
= PrincipalToPrincipalInfo(principal
, &principalInfo
);
3229 if (NS_WARN_IF(NS_FAILED(rv
))) {
3230 return LocalDevicePromise::CreateAndReject(
3231 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::SecurityError
),
3234 uint64_t windowID
= aWindow
->WindowID();
3235 const bool resistFingerprinting
=
3236 aWindow
->AsGlobal()->ShouldResistFingerprinting(aCallerType
,
3237 RFPTarget::MediaDevices
);
3238 return EnumerateDevicesImpl(
3239 aWindow
, CreateEnumerationParams(
3240 MediaSourceEnum::Other
, MediaSourceEnum::Other
,
3241 {EnumerationFlag::EnumerateAudioOutputs
,
3242 EnumerationFlag::AllowPermissionRequest
}))
3244 GetCurrentSerialEventTarget(), __func__
,
3245 [self
= RefPtr
<MediaManager
>(this), windowID
, aOptions
, aCallerType
,
3246 resistFingerprinting
, isHandlingUserInput
,
3247 principalInfo
](RefPtr
<LocalMediaDeviceSetRefCnt
> aDevices
) mutable {
3248 // Ensure that the window is still good.
3249 RefPtr
<nsPIDOMWindowInner
> window
=
3250 nsGlobalWindowInner::GetInnerWindowWithId(windowID
);
3252 LOG("SelectAudioOutput: bad window (%" PRIu64
3253 ") in post enumeration success callback!",
3255 return LocalDevicePromise::CreateAndReject(
3256 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
),
3259 if (aDevices
->IsEmpty()) {
3260 LOG("SelectAudioOutput: no devices found");
3261 auto error
= resistFingerprinting
3262 ? MediaMgrError::Name::NotAllowedError
3263 : MediaMgrError::Name::NotFoundError
;
3264 return LocalDevicePromise::CreateAndReject(
3265 MakeRefPtr
<MediaMgrError
>(error
), __func__
);
3267 MozPromiseHolder
<LocalDevicePromise
> holder
;
3268 RefPtr
<LocalDevicePromise
> p
= holder
.Ensure(__func__
);
3269 auto task
= MakeRefPtr
<SelectAudioOutputTask
>(
3270 std::move(holder
), windowID
, aCallerType
, principalInfo
);
3272 nsresult rv
= GenerateUUID(callID
);
3273 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
));
3275 self
->AddTaskAndGetCount(windowID
, callID
, std::move(task
));
3276 bool askPermission
=
3277 !Preferences::GetBool("media.navigator.permission.disabled") ||
3278 Preferences::GetBool("media.navigator.permission.force");
3279 if (!askPermission
) {
3280 self
->NotifyAllowed(callID
, *aDevices
);
3282 MOZ_ASSERT(window
->IsSecureContext());
3283 auto req
= MakeRefPtr
<GetUserMediaRequest
>(
3284 window
, callID
, std::move(aDevices
), aOptions
, true,
3285 isHandlingUserInput
);
3286 if (taskCount
> 1) {
3287 // there is at least 1 pending gUM request
3288 self
->mPendingGUMRequest
.AppendElement(req
.forget());
3290 nsCOMPtr
<nsIObserverService
> obs
=
3291 services::GetObserverService();
3292 obs
->NotifyObservers(req
, "getUserMedia:request", nullptr);
3297 [](RefPtr
<MediaMgrError
> aError
) {
3298 LOG("SelectAudioOutput: EnumerateDevicesImpl "
3299 "failure callback called!");
3300 return LocalDevicePromise::CreateAndReject(std::move(aError
),
3305 MediaEngine
* MediaManager::GetBackend() {
3306 MOZ_ASSERT(MediaManager::IsInMediaThread());
3307 // Plugin backends as appropriate. The default engine also currently
3308 // includes picture support for Android.
3309 // This IS called off main-thread.
3311 #if defined(MOZ_WEBRTC)
3312 mBackend
= new MediaEngineWebRTC();
3314 mBackend
= new MediaEngineFake();
3316 mDeviceListChangeListener
= mBackend
->DeviceListChangeEvent().Connect(
3317 AbstractThread::MainThread(), this, &MediaManager::DeviceListChanged
);
3322 void MediaManager::OnNavigation(uint64_t aWindowID
) {
3323 MOZ_ASSERT(NS_IsMainThread());
3324 LOG("OnNavigation for %" PRIu64
, aWindowID
);
3326 // Stop the streams for this window. The runnables check this value before
3327 // making a call to content.
3329 nsTArray
<nsString
>* callIDs
;
3330 if (mCallIds
.Get(aWindowID
, &callIDs
)) {
3331 for (auto& callID
: *callIDs
) {
3332 mActiveCallbacks
.Remove(callID
);
3333 for (auto& request
: mPendingGUMRequest
.Clone()) {
3335 request
->GetCallID(id
);
3337 mPendingGUMRequest
.RemoveElement(request
);
3341 mCallIds
.Remove(aWindowID
);
3344 if (RefPtr
<GetUserMediaWindowListener
> listener
=
3345 GetWindowListener(aWindowID
)) {
3346 listener
->RemoveAll();
3348 MOZ_ASSERT(!GetWindowListener(aWindowID
));
3351 void MediaManager::OnCameraMute(bool aMute
) {
3352 MOZ_ASSERT(NS_IsMainThread());
3353 LOG("OnCameraMute for all windows");
3354 mCamerasMuted
= aMute
;
3355 // This is safe since we're on main-thread, and the windowlist can only
3356 // be added to from the main-thread
3357 for (const auto& window
: mActiveWindows
.Values()) {
3358 window
->MuteOrUnmuteCameras(aMute
);
3362 void MediaManager::OnMicrophoneMute(bool aMute
) {
3363 MOZ_ASSERT(NS_IsMainThread());
3364 LOG("OnMicrophoneMute for all windows");
3365 mMicrophonesMuted
= aMute
;
3366 // This is safe since we're on main-thread, and the windowlist can only
3367 // be added to from the main-thread
3368 for (const auto& window
: mActiveWindows
.Values()) {
3369 window
->MuteOrUnmuteMicrophones(aMute
);
3373 RefPtr
<GetUserMediaWindowListener
> MediaManager::GetOrMakeWindowListener(
3374 nsPIDOMWindowInner
* aWindow
) {
3375 Document
* doc
= aWindow
->GetExtantDoc();
3377 // The window has been destroyed. Destroyed windows don't have listeners.
3380 nsIPrincipal
* principal
= doc
->NodePrincipal();
3381 uint64_t windowId
= aWindow
->WindowID();
3382 RefPtr
<GetUserMediaWindowListener
> windowListener
=
3383 GetWindowListener(windowId
);
3384 if (windowListener
) {
3385 MOZ_ASSERT(PrincipalHandleMatches(windowListener
->GetPrincipalHandle(),
3388 windowListener
= new GetUserMediaWindowListener(
3389 windowId
, MakePrincipalHandle(principal
));
3390 AddWindowID(windowId
, windowListener
);
3392 return windowListener
;
3395 void MediaManager::AddWindowID(uint64_t aWindowId
,
3396 RefPtr
<GetUserMediaWindowListener
> aListener
) {
3397 MOZ_ASSERT(NS_IsMainThread());
3398 // Store the WindowID in a hash table and mark as active. The entry is removed
3399 // when this window is closed or navigated away from.
3400 // This is safe since we're on main-thread, and the windowlist can only
3401 // be invalidated from the main-thread (see OnNavigation)
3402 if (IsWindowStillActive(aWindowId
)) {
3403 MOZ_ASSERT(false, "Window already added");
3407 aListener
->MuteOrUnmuteCameras(mCamerasMuted
);
3408 aListener
->MuteOrUnmuteMicrophones(mMicrophonesMuted
);
3409 GetActiveWindows()->InsertOrUpdate(aWindowId
, std::move(aListener
));
3411 RefPtr
<WindowGlobalChild
> wgc
=
3412 WindowGlobalChild::GetByInnerWindowId(aWindowId
);
3414 wgc
->BlockBFCacheFor(BFCacheStatus::ACTIVE_GET_USER_MEDIA
);
3418 void MediaManager::RemoveWindowID(uint64_t aWindowId
) {
3419 RefPtr
<WindowGlobalChild
> wgc
=
3420 WindowGlobalChild::GetByInnerWindowId(aWindowId
);
3422 wgc
->UnblockBFCacheFor(BFCacheStatus::ACTIVE_GET_USER_MEDIA
);
3425 mActiveWindows
.Remove(aWindowId
);
3427 // get outer windowID
3428 auto* window
= nsGlobalWindowInner::GetInnerWindowWithId(aWindowId
);
3430 LOG("No inner window for %" PRIu64
, aWindowId
);
3434 auto* outer
= window
->GetOuterWindow();
3436 LOG("No outer window for inner %" PRIu64
, aWindowId
);
3440 uint64_t outerID
= outer
->WindowID();
3442 // Notify the UI that this window no longer has gUM active
3443 char windowBuffer
[32];
3444 SprintfLiteral(windowBuffer
, "%" PRIu64
, outerID
);
3445 nsString data
= NS_ConvertUTF8toUTF16(windowBuffer
);
3447 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
3448 obs
->NotifyWhenScriptSafe(nullptr, "recording-window-ended", data
.get());
3449 LOG("Sent recording-window-ended for window %" PRIu64
" (outer %" PRIu64
")",
3450 aWindowId
, outerID
);
3453 bool MediaManager::IsWindowListenerStillActive(
3454 const RefPtr
<GetUserMediaWindowListener
>& aListener
) {
3455 MOZ_DIAGNOSTIC_ASSERT(aListener
);
3456 return aListener
&& aListener
== GetWindowListener(aListener
->WindowID());
3459 void MediaManager::GetPref(nsIPrefBranch
* aBranch
, const char* aPref
,
3460 const char* aData
, int32_t* aVal
) {
3462 if (aData
== nullptr || strcmp(aPref
, aData
) == 0) {
3463 if (NS_SUCCEEDED(aBranch
->GetIntPref(aPref
, &temp
))) {
3469 void MediaManager::GetPrefBool(nsIPrefBranch
* aBranch
, const char* aPref
,
3470 const char* aData
, bool* aVal
) {
3472 if (aData
== nullptr || strcmp(aPref
, aData
) == 0) {
3473 if (NS_SUCCEEDED(aBranch
->GetBoolPref(aPref
, &temp
))) {
3479 void MediaManager::GetPrefs(nsIPrefBranch
* aBranch
, const char* aData
) {
3480 GetPref(aBranch
, "media.navigator.video.default_width", aData
,
3482 GetPref(aBranch
, "media.navigator.video.default_height", aData
,
3484 GetPref(aBranch
, "media.navigator.video.default_fps", aData
, &mPrefs
.mFPS
);
3485 GetPref(aBranch
, "media.navigator.audio.fake_frequency", aData
,
3488 GetPrefBool(aBranch
, "media.getusermedia.aec_enabled", aData
, &mPrefs
.mAecOn
);
3489 GetPrefBool(aBranch
, "media.getusermedia.agc_enabled", aData
, &mPrefs
.mAgcOn
);
3490 GetPrefBool(aBranch
, "media.getusermedia.hpf_enabled", aData
, &mPrefs
.mHPFOn
);
3491 GetPrefBool(aBranch
, "media.getusermedia.noise_enabled", aData
,
3493 GetPrefBool(aBranch
, "media.getusermedia.transient_enabled", aData
,
3494 &mPrefs
.mTransientOn
);
3495 GetPrefBool(aBranch
, "media.getusermedia.agc2_forced", aData
,
3496 &mPrefs
.mAgc2Forced
);
3497 GetPref(aBranch
, "media.getusermedia.agc", aData
, &mPrefs
.mAgc
);
3498 GetPref(aBranch
, "media.getusermedia.noise", aData
, &mPrefs
.mNoise
);
3499 GetPref(aBranch
, "media.getusermedia.channels", aData
, &mPrefs
.mChannels
);
3503 void MediaManager::Shutdown() {
3504 MOZ_ASSERT(NS_IsMainThread());
3505 if (sHasMainThreadShutdown
) {
3509 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
3511 obs
->RemoveObserver(this, "last-pb-context-exited");
3512 obs
->RemoveObserver(this, "getUserMedia:privileged:allow");
3513 obs
->RemoveObserver(this, "getUserMedia:response:allow");
3514 obs
->RemoveObserver(this, "getUserMedia:response:deny");
3515 obs
->RemoveObserver(this, "getUserMedia:response:noOSPermission");
3516 obs
->RemoveObserver(this, "getUserMedia:revoke");
3517 obs
->RemoveObserver(this, "getUserMedia:muteVideo");
3518 obs
->RemoveObserver(this, "getUserMedia:unmuteVideo");
3519 obs
->RemoveObserver(this, "getUserMedia:muteAudio");
3520 obs
->RemoveObserver(this, "getUserMedia:unmuteAudio");
3521 obs
->RemoveObserver(this, "application-background");
3522 obs
->RemoveObserver(this, "application-foreground");
3524 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
3526 ForeachObservedPref([&](const nsLiteralCString
& aPrefName
) {
3527 prefs
->RemoveObserver(aPrefName
, this);
3531 if (mDeviceChangeTimer
) {
3532 mDeviceChangeTimer
->Cancel();
3533 // Drop ref to MediaTimer early to avoid blocking SharedThreadPool shutdown
3534 mDeviceChangeTimer
= nullptr;
3538 // Close off any remaining active windows.
3540 // Live capture at this point is rare but can happen. Stopping it will make
3541 // the window listeners attempt to remove themselves from the active windows
3542 // table. We cannot touch the table at point so we grab a copy of the window
3544 const auto listeners
= ToArray(GetActiveWindows()->Values());
3545 for (const auto& listener
: listeners
) {
3546 listener
->RemoveAll();
3549 MOZ_ASSERT(GetActiveWindows()->Count() == 0);
3551 GetActiveWindows()->Clear();
3552 mActiveCallbacks
.Clear();
3554 mPendingGUMRequest
.Clear();
3556 mLogHandle
= nullptr;
3559 // From main thread's point of view, shutdown is now done.
3560 // All that remains is shutting down the media thread.
3561 sHasMainThreadShutdown
= true;
3563 // Release the backend (and call Shutdown()) from within mMediaThread.
3564 // Don't use MediaManager::Dispatch() because we're
3565 // sHasMainThreadShutdown == true here!
3566 MOZ_ALWAYS_SUCCEEDS(mMediaThread
->Dispatch(
3567 NS_NewRunnableFunction(__func__
, [self
= RefPtr(this), this]() {
3568 LOG("MediaManager Thread Shutdown");
3569 MOZ_ASSERT(IsInMediaThread());
3570 // Must shutdown backend on MediaManager thread, since that's
3571 // where we started it from!
3573 mBackend
->Shutdown(); // idempotent
3574 mDeviceListChangeListener
.DisconnectIfExists();
3576 // last reference, will invoke Shutdown() again
3580 // note that this == sSingleton
3581 MOZ_ASSERT(this == sSingleton
);
3583 // Explicitly shut down the TaskQueue so that it releases its
3584 // SharedThreadPool when all tasks have completed. SharedThreadPool blocks
3585 // XPCOM shutdown from proceeding beyond "xpcom-shutdown-threads" until all
3586 // SharedThreadPools are released, but the nsComponentManager keeps a
3587 // reference to the MediaManager for the nsIMediaManagerService until much
3588 // later in shutdown. This also provides additional assurance that no
3589 // further tasks will be queued.
3590 mMediaThread
->BeginShutdown()->Then(
3591 GetMainThreadSerialEventTarget(), __func__
, [] {
3592 LOG("MediaManager shutdown lambda running, releasing MediaManager "
3594 // Remove async shutdown blocker
3595 media::MustGetShutdownBarrier()->RemoveBlocker(
3596 sSingleton
->mShutdownBlocker
);
3598 sSingleton
= nullptr;
3602 void MediaManager::SendPendingGUMRequest() {
3603 if (mPendingGUMRequest
.Length() > 0) {
3604 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
3605 obs
->NotifyObservers(mPendingGUMRequest
[0], "getUserMedia:request",
3607 mPendingGUMRequest
.RemoveElementAt(0);
3611 bool IsGUMResponseNoAccess(const char* aTopic
,
3612 MediaMgrError::Name
& aErrorName
) {
3613 if (!strcmp(aTopic
, "getUserMedia:response:deny")) {
3614 aErrorName
= MediaMgrError::Name::NotAllowedError
;
3618 if (!strcmp(aTopic
, "getUserMedia:response:noOSPermission")) {
3619 aErrorName
= MediaMgrError::Name::NotFoundError
;
3626 static MediaSourceEnum
ParseScreenColonWindowID(const char16_t
* aData
,
3627 uint64_t* aWindowIDOut
) {
3628 MOZ_ASSERT(aWindowIDOut
);
3629 // may be windowid or screen:windowid
3630 const nsDependentString
data(aData
);
3631 if (Substring(data
, 0, strlen("screen:")).EqualsLiteral("screen:")) {
3633 *aWindowIDOut
= Substring(data
, strlen("screen:")).ToInteger64(&rv
);
3634 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
));
3635 return MediaSourceEnum::Screen
;
3638 *aWindowIDOut
= data
.ToInteger64(&rv
);
3639 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
));
3640 return MediaSourceEnum::Camera
;
3643 nsresult
MediaManager::Observe(nsISupports
* aSubject
, const char* aTopic
,
3644 const char16_t
* aData
) {
3645 MOZ_ASSERT(NS_IsMainThread());
3647 MediaMgrError::Name gumNoAccessError
= MediaMgrError::Name::NotAllowedError
;
3649 if (!strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
)) {
3650 nsCOMPtr
<nsIPrefBranch
> branch(do_QueryInterface(aSubject
));
3652 GetPrefs(branch
, NS_ConvertUTF16toUTF8(aData
).get());
3653 LOG("%s: %dx%d @%dfps", __FUNCTION__
, mPrefs
.mWidth
, mPrefs
.mHeight
,
3655 DeviceListChanged();
3657 } else if (!strcmp(aTopic
, "last-pb-context-exited")) {
3658 // Clear memory of private-browsing-specific deviceIds. Fire and forget.
3659 media::SanitizeOriginKeys(0, true);
3661 } else if (!strcmp(aTopic
, "getUserMedia:got-device-permission")) {
3662 MOZ_ASSERT(aSubject
);
3663 nsCOMPtr
<nsIRunnable
> task
= do_QueryInterface(aSubject
);
3664 MediaManager::Dispatch(NewTaskFrom([task
] { task
->Run(); }));
3666 } else if (!strcmp(aTopic
, "getUserMedia:privileged:allow") ||
3667 !strcmp(aTopic
, "getUserMedia:response:allow")) {
3668 nsString
key(aData
);
3669 RefPtr
<GetUserMediaTask
> task
= TakeGetUserMediaTask(key
);
3674 if (sHasMainThreadShutdown
) {
3675 task
->Denied(MediaMgrError::Name::AbortError
, "In shutdown"_ns
);
3678 if (NS_WARN_IF(!aSubject
)) {
3679 return NS_ERROR_FAILURE
; // ignored
3681 // Permission has been granted. aSubject contains the particular device
3682 // or devices selected and approved by the user, if any.
3683 nsCOMPtr
<nsIArray
> array(do_QueryInterface(aSubject
));
3686 array
->GetLength(&len
);
3687 RefPtr
<LocalMediaDevice
> audioInput
;
3688 RefPtr
<LocalMediaDevice
> videoInput
;
3689 RefPtr
<LocalMediaDevice
> audioOutput
;
3690 for (uint32_t i
= 0; i
< len
; i
++) {
3691 nsCOMPtr
<nsIMediaDevice
> device
;
3692 array
->QueryElementAt(i
, NS_GET_IID(nsIMediaDevice
),
3693 getter_AddRefs(device
));
3694 MOZ_ASSERT(device
); // shouldn't be returning anything else...
3699 // Casting here is safe because a LocalMediaDevice is created
3700 // only in Gecko side, JS can only query for an instance.
3701 auto* dev
= static_cast<LocalMediaDevice
*>(device
.get());
3702 switch (dev
->Kind()) {
3703 case MediaDeviceKind::Videoinput
:
3708 case MediaDeviceKind::Audioinput
:
3713 case MediaDeviceKind::Audiooutput
:
3719 MOZ_CRASH("Unexpected device kind");
3723 if (GetUserMediaStreamTask
* streamTask
= task
->AsGetUserMediaStreamTask()) {
3724 bool needVideo
= IsOn(streamTask
->GetConstraints().mVideo
);
3725 bool needAudio
= IsOn(streamTask
->GetConstraints().mAudio
);
3726 MOZ_ASSERT(needVideo
|| needAudio
);
3728 if ((needVideo
&& !videoInput
) || (needAudio
&& !audioInput
)) {
3729 task
->Denied(MediaMgrError::Name::NotAllowedError
);
3732 streamTask
->Allowed(std::move(audioInput
), std::move(videoInput
));
3735 if (SelectAudioOutputTask
* outputTask
= task
->AsSelectAudioOutputTask()) {
3737 task
->Denied(MediaMgrError::Name::NotAllowedError
);
3740 outputTask
->Allowed(std::move(audioOutput
));
3744 NS_WARNING("Unknown task type in getUserMedia");
3745 return NS_ERROR_FAILURE
;
3747 } else if (IsGUMResponseNoAccess(aTopic
, gumNoAccessError
)) {
3748 nsString
key(aData
);
3749 RefPtr
<GetUserMediaTask
> task
= TakeGetUserMediaTask(key
);
3751 task
->Denied(gumNoAccessError
);
3752 SendPendingGUMRequest();
3756 } else if (!strcmp(aTopic
, "getUserMedia:revoke")) {
3758 if (ParseScreenColonWindowID(aData
, &windowID
) == MediaSourceEnum::Screen
) {
3759 LOG("Revoking ScreenCapture access for window %" PRIu64
, windowID
);
3760 StopScreensharing(windowID
);
3762 LOG("Revoking MediaCapture access for window %" PRIu64
, windowID
);
3763 OnNavigation(windowID
);
3766 } else if (!strcmp(aTopic
, "getUserMedia:muteVideo") ||
3767 !strcmp(aTopic
, "getUserMedia:unmuteVideo")) {
3768 OnCameraMute(!strcmp(aTopic
, "getUserMedia:muteVideo"));
3770 } else if (!strcmp(aTopic
, "getUserMedia:muteAudio") ||
3771 !strcmp(aTopic
, "getUserMedia:unmuteAudio")) {
3772 OnMicrophoneMute(!strcmp(aTopic
, "getUserMedia:muteAudio"));
3774 } else if ((!strcmp(aTopic
, "application-background") ||
3775 !strcmp(aTopic
, "application-foreground")) &&
3776 StaticPrefs::media_getusermedia_camera_background_mute_enabled()) {
3777 // On mobile we turn off any cameras (but not mics) while in the background.
3778 // Keeping things simple for now by duplicating test-covered code above.
3780 // NOTE: If a mobile device ever wants to implement "getUserMedia:muteVideo"
3781 // as well, it'd need to update this code to handle & test the combinations.
3782 OnCameraMute(!strcmp(aTopic
, "application-background"));
3789 MediaManager::CollectReports(nsIHandleReportCallback
* aHandleReport
,
3790 nsISupports
* aData
, bool aAnonymize
) {
3792 amount
+= mActiveWindows
.ShallowSizeOfExcludingThis(MallocSizeOf
);
3793 for (const GetUserMediaWindowListener
* listener
: mActiveWindows
.Values()) {
3794 amount
+= listener
->SizeOfIncludingThis(MallocSizeOf
);
3796 amount
+= mActiveCallbacks
.ShallowSizeOfExcludingThis(MallocSizeOf
);
3797 for (const GetUserMediaTask
* task
: mActiveCallbacks
.Values()) {
3798 // Assume nsString buffers for keys are accounted in mCallIds.
3799 amount
+= task
->SizeOfIncludingThis(MallocSizeOf
);
3801 amount
+= mCallIds
.ShallowSizeOfExcludingThis(MallocSizeOf
);
3802 for (const auto& array
: mCallIds
.Values()) {
3803 amount
+= array
->ShallowSizeOfExcludingThis(MallocSizeOf
);
3804 for (const nsString
& callID
: *array
) {
3805 amount
+= callID
.SizeOfExcludingThisEvenIfShared(MallocSizeOf
);
3808 amount
+= mPendingGUMRequest
.ShallowSizeOfExcludingThis(MallocSizeOf
);
3809 // GetUserMediaRequest pointees of mPendingGUMRequest do not have support
3810 // for memory accounting. mPendingGUMRequest logic should probably be moved
3811 // to the front end (bug 1691625).
3812 MOZ_COLLECT_REPORT("explicit/media/media-manager-aggregates", KIND_HEAP
,
3813 UNITS_BYTES
, amount
,
3814 "Memory used by MediaManager variable length members.");
3818 nsresult
MediaManager::GetActiveMediaCaptureWindows(nsIArray
** aArray
) {
3821 nsCOMPtr
<nsIMutableArray
> array
= nsArray::Create();
3823 for (const auto& entry
: mActiveWindows
) {
3824 const uint64_t& id
= entry
.GetKey();
3825 RefPtr
<GetUserMediaWindowListener
> winListener
= entry
.GetData();
3830 auto* window
= nsGlobalWindowInner::GetInnerWindowWithId(id
);
3837 if (winListener
->CapturingVideo() || winListener
->CapturingAudio()) {
3838 array
->AppendElement(ToSupports(window
));
3842 array
.forget(aArray
);
3846 struct CaptureWindowStateData
{
3848 uint16_t* mMicrophone
;
3849 uint16_t* mScreenShare
;
3850 uint16_t* mWindowShare
;
3851 uint16_t* mAppShare
;
3852 uint16_t* mBrowserShare
;
3856 MediaManager::MediaCaptureWindowState(
3857 nsIDOMWindow
* aCapturedWindow
, uint16_t* aCamera
, uint16_t* aMicrophone
,
3858 uint16_t* aScreen
, uint16_t* aWindow
, uint16_t* aBrowser
,
3859 nsTArray
<RefPtr
<nsIMediaDevice
>>& aDevices
) {
3860 MOZ_ASSERT(NS_IsMainThread());
3862 CaptureState camera
= CaptureState::Off
;
3863 CaptureState microphone
= CaptureState::Off
;
3864 CaptureState screen
= CaptureState::Off
;
3865 CaptureState window
= CaptureState::Off
;
3866 CaptureState browser
= CaptureState::Off
;
3867 RefPtr
<LocalMediaDeviceSetRefCnt
> devices
;
3869 nsCOMPtr
<nsPIDOMWindowInner
> piWin
= do_QueryInterface(aCapturedWindow
);
3871 if (RefPtr
<GetUserMediaWindowListener
> listener
=
3872 GetWindowListener(piWin
->WindowID())) {
3873 camera
= listener
->CapturingSource(MediaSourceEnum::Camera
);
3874 microphone
= listener
->CapturingSource(MediaSourceEnum::Microphone
);
3875 screen
= listener
->CapturingSource(MediaSourceEnum::Screen
);
3876 window
= listener
->CapturingSource(MediaSourceEnum::Window
);
3877 browser
= listener
->CapturingSource(MediaSourceEnum::Browser
);
3878 devices
= listener
->GetDevices();
3882 *aCamera
= FromCaptureState(camera
);
3883 *aMicrophone
= FromCaptureState(microphone
);
3884 *aScreen
= FromCaptureState(screen
);
3885 *aWindow
= FromCaptureState(window
);
3886 *aBrowser
= FromCaptureState(browser
);
3888 for (auto& device
: *devices
) {
3889 aDevices
.AppendElement(device
);
3893 LOG("%s: window %" PRIu64
" capturing %s %s %s %s %s", __FUNCTION__
,
3894 piWin
? piWin
->WindowID() : -1,
3895 *aCamera
== nsIMediaManagerService::STATE_CAPTURE_ENABLED
3896 ? "camera (enabled)"
3897 : (*aCamera
== nsIMediaManagerService::STATE_CAPTURE_DISABLED
3898 ? "camera (disabled)"
3900 *aMicrophone
== nsIMediaManagerService::STATE_CAPTURE_ENABLED
3901 ? "microphone (enabled)"
3902 : (*aMicrophone
== nsIMediaManagerService::STATE_CAPTURE_DISABLED
3903 ? "microphone (disabled)"
3905 *aScreen
? "screenshare" : "", *aWindow
? "windowshare" : "",
3906 *aBrowser
? "browsershare" : "");
3912 MediaManager::SanitizeDeviceIds(int64_t aSinceWhen
) {
3913 MOZ_ASSERT(NS_IsMainThread());
3914 LOG("%s: sinceWhen = %" PRId64
, __FUNCTION__
, aSinceWhen
);
3916 media::SanitizeOriginKeys(aSinceWhen
, false); // we fire and forget
3920 void MediaManager::StopScreensharing(uint64_t aWindowID
) {
3921 // We need to stop window/screensharing for all streams in this innerwindow.
3923 if (RefPtr
<GetUserMediaWindowListener
> listener
=
3924 GetWindowListener(aWindowID
)) {
3925 listener
->StopSharing();
3929 bool MediaManager::IsActivelyCapturingOrHasAPermission(uint64_t aWindowId
) {
3930 // Does page currently have a gUM stream active?
3932 nsCOMPtr
<nsIArray
> array
;
3933 GetActiveMediaCaptureWindows(getter_AddRefs(array
));
3935 array
->GetLength(&len
);
3936 for (uint32_t i
= 0; i
< len
; i
++) {
3937 nsCOMPtr
<nsPIDOMWindowInner
> win
;
3938 array
->QueryElementAt(i
, NS_GET_IID(nsPIDOMWindowInner
),
3939 getter_AddRefs(win
));
3940 if (win
&& win
->WindowID() == aWindowId
) {
3945 // Or are persistent permissions (audio or video) granted?
3947 auto* window
= nsGlobalWindowInner::GetInnerWindowWithId(aWindowId
);
3948 if (NS_WARN_IF(!window
) || NS_WARN_IF(!window
->GetPrincipal())) {
3952 Document
* doc
= window
->GetExtantDoc();
3953 if (NS_WARN_IF(!doc
)) {
3957 nsIPrincipal
* principal
= window
->GetPrincipal();
3958 if (NS_WARN_IF(!principal
)) {
3962 // Check if this site has persistent permissions.
3964 RefPtr
<PermissionDelegateHandler
> permDelegate
=
3965 doc
->GetPermissionDelegateHandler();
3966 if (NS_WARN_IF(!permDelegate
)) {
3970 uint32_t audio
= nsIPermissionManager::UNKNOWN_ACTION
;
3971 uint32_t video
= nsIPermissionManager::UNKNOWN_ACTION
;
3973 rv
= permDelegate
->GetPermission("microphone"_ns
, &audio
, true);
3974 if (NS_WARN_IF(NS_FAILED(rv
))) {
3977 rv
= permDelegate
->GetPermission("camera"_ns
, &video
, true);
3978 if (NS_WARN_IF(NS_FAILED(rv
))) {
3982 return audio
== nsIPermissionManager::ALLOW_ACTION
||
3983 video
== nsIPermissionManager::ALLOW_ACTION
;
3986 DeviceListener::DeviceListener()
3988 mMainThreadCheck(nullptr),
3989 mPrincipalHandle(PRINCIPAL_HANDLE_NONE
),
3990 mWindowListener(nullptr) {}
3992 void DeviceListener::Register(GetUserMediaWindowListener
* aListener
) {
3993 LOG("DeviceListener %p registering with window listener %p", this, aListener
);
3995 MOZ_ASSERT(aListener
, "No listener");
3996 MOZ_ASSERT(!mWindowListener
, "Already registered");
3997 MOZ_ASSERT(!Activated(), "Already activated");
3999 mPrincipalHandle
= aListener
->GetPrincipalHandle();
4000 mWindowListener
= aListener
;
4003 void DeviceListener::Activate(RefPtr
<LocalMediaDevice
> aDevice
,
4004 RefPtr
<LocalTrackSource
> aTrackSource
,
4006 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4008 LOG("DeviceListener %p activating %s device %p", this,
4009 nsCString(dom::MediaDeviceKindValues::GetString(aDevice
->Kind())).get(),
4012 MOZ_ASSERT(!mStopped
, "Cannot activate stopped device listener");
4013 MOZ_ASSERT(!Activated(), "Already activated");
4015 mMainThreadCheck
= PR_GetCurrentThread();
4016 bool offWhileDisabled
=
4017 (aDevice
->GetMediaSource() == MediaSourceEnum::Microphone
&&
4018 Preferences::GetBool(
4019 "media.getusermedia.microphone.off_while_disabled.enabled", true)) ||
4020 (aDevice
->GetMediaSource() == MediaSourceEnum::Camera
&&
4021 Preferences::GetBool(
4022 "media.getusermedia.camera.off_while_disabled.enabled", true));
4023 mDeviceState
= MakeUnique
<DeviceState
>(
4024 std::move(aDevice
), std::move(aTrackSource
), offWhileDisabled
);
4025 mDeviceState
->mDeviceMuted
= aStartMuted
;
4027 mDeviceState
->mTrackSource
->Mute();
4031 RefPtr
<DeviceListener::DeviceListenerPromise
>
4032 DeviceListener::InitializeAsync() {
4033 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4034 MOZ_DIAGNOSTIC_ASSERT(!mStopped
);
4036 return MediaManager::Dispatch
<DeviceListenerPromise
>(
4038 [principal
= GetPrincipalHandle(), device
= mDeviceState
->mDevice
,
4039 track
= mDeviceState
->mTrackSource
->mTrack
,
4040 deviceMuted
= mDeviceState
->mDeviceMuted
](
4041 MozPromiseHolder
<DeviceListenerPromise
>& aHolder
) {
4042 auto kind
= device
->Kind();
4043 device
->SetTrack(track
, principal
);
4044 nsresult rv
= deviceMuted
? NS_OK
: device
->Start();
4045 if (kind
== MediaDeviceKind::Audioinput
||
4046 kind
== MediaDeviceKind::Videoinput
) {
4047 if ((rv
== NS_ERROR_NOT_AVAILABLE
&&
4048 kind
== MediaDeviceKind::Audioinput
) ||
4049 (NS_FAILED(rv
) && kind
== MediaDeviceKind::Videoinput
)) {
4051 rv
= device
->Start();
4053 if (rv
== NS_ERROR_NOT_AVAILABLE
&&
4054 kind
== MediaDeviceKind::Audioinput
) {
4056 log
.AssignLiteral("Concurrent mic process limit.");
4057 aHolder
.Reject(MakeRefPtr
<MediaMgrError
>(
4058 MediaMgrError::Name::NotReadableError
,
4064 if (NS_FAILED(rv
)) {
4067 "Starting %s failed",
4068 nsCString(dom::MediaDeviceKindValues::GetString(kind
))
4071 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::AbortError
,
4076 LOG("started %s device %p",
4077 nsCString(dom::MediaDeviceKindValues::GetString(kind
)).get(),
4079 aHolder
.Resolve(true, __func__
);
4082 GetMainThreadSerialEventTarget(), __func__
,
4083 [self
= RefPtr
<DeviceListener
>(this), this]() {
4085 // We were shut down during the async init
4086 return DeviceListenerPromise::CreateAndResolve(true, __func__
);
4089 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mTrackEnabled
);
4090 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mDeviceEnabled
);
4091 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mStopped
);
4093 mDeviceState
->mDeviceEnabled
= true;
4094 mDeviceState
->mTrackEnabled
= true;
4095 mDeviceState
->mTrackEnabledTime
= TimeStamp::Now();
4096 return DeviceListenerPromise::CreateAndResolve(true, __func__
);
4098 [self
= RefPtr
<DeviceListener
>(this),
4099 this](RefPtr
<MediaMgrError
>&& aResult
) {
4101 return DeviceListenerPromise::CreateAndReject(std::move(aResult
),
4105 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mTrackEnabled
);
4106 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mDeviceEnabled
);
4107 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState
->mStopped
);
4110 return DeviceListenerPromise::CreateAndReject(std::move(aResult
),
4115 void DeviceListener::Stop() {
4116 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4123 LOG("DeviceListener %p stopping", this);
4126 mDeviceState
->mDisableTimer
->Cancel();
4128 if (mDeviceState
->mStopped
) {
4129 // device already stopped.
4132 mDeviceState
->mStopped
= true;
4134 mDeviceState
->mTrackSource
->Stop();
4136 MediaManager::Dispatch(NewTaskFrom([device
= mDeviceState
->mDevice
]() {
4138 device
->Deallocate();
4141 mWindowListener
->ChromeAffectingStateChanged();
4144 // Keep a strong ref to the removed window listener.
4145 RefPtr
<GetUserMediaWindowListener
> windowListener
= mWindowListener
;
4146 mWindowListener
= nullptr;
4147 windowListener
->Remove(this);
4150 void DeviceListener::GetSettings(MediaTrackSettings
& aOutSettings
) const {
4151 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4152 LocalMediaDevice
* device
= GetDevice();
4153 device
->GetSettings(aOutSettings
);
4155 MediaSourceEnum mediaSource
= device
->GetMediaSource();
4156 if (mediaSource
== MediaSourceEnum::Camera
||
4157 mediaSource
== MediaSourceEnum::Microphone
) {
4158 aOutSettings
.mDeviceId
.Construct(device
->mID
);
4159 aOutSettings
.mGroupId
.Construct(device
->mGroupID
);
4163 auto DeviceListener::UpdateDevice(bool aOn
) -> RefPtr
<DeviceOperationPromise
> {
4164 MOZ_ASSERT(NS_IsMainThread());
4165 RefPtr
<DeviceListener
> self
= this;
4166 DeviceState
& state
= *mDeviceState
;
4167 return MediaManager::Dispatch
<DeviceOperationPromise
>(
4169 [self
, device
= state
.mDevice
,
4170 aOn
](MozPromiseHolder
<DeviceOperationPromise
>& h
) {
4171 LOG("Turning %s device (%s)", aOn
? "on" : "off",
4172 NS_ConvertUTF16toUTF8(device
->mName
).get());
4173 h
.Resolve(aOn
? device
->Start() : device
->Stop(), __func__
);
4176 GetMainThreadSerialEventTarget(), __func__
,
4177 [self
, this, &state
, aOn
](nsresult aResult
) {
4178 if (state
.mStopped
) {
4179 // Device was stopped on main thread during the operation. Done.
4180 return DeviceOperationPromise::CreateAndResolve(aResult
,
4183 LOG("DeviceListener %p turning %s %s input device %s", this,
4186 dom::MediaDeviceKindValues::GetString(GetDevice()->Kind()))
4188 NS_SUCCEEDED(aResult
) ? "succeeded" : "failed");
4190 if (NS_FAILED(aResult
) && aResult
!= NS_ERROR_ABORT
) {
4191 // This path handles errors from starting or stopping the
4192 // device. NS_ERROR_ABORT are for cases where *we* aborted. They
4193 // need graceful handling.
4195 // Starting the device failed. Stopping the track here will
4196 // make the MediaStreamTrack end after a pass through the
4200 // Stopping the device failed. This is odd, but not fatal.
4201 MOZ_ASSERT_UNREACHABLE("The device should be stoppable");
4204 return DeviceOperationPromise::CreateAndResolve(aResult
, __func__
);
4207 MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject");
4208 return DeviceOperationPromise::CreateAndReject(false, __func__
);
4212 void DeviceListener::SetDeviceEnabled(bool aEnable
) {
4213 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4214 MOZ_ASSERT(Activated(), "No device to set enabled state for");
4216 DeviceState
& state
= *mDeviceState
;
4218 LOG("DeviceListener %p %s %s device", this,
4219 aEnable
? "enabling" : "disabling",
4220 nsCString(dom::MediaDeviceKindValues::GetString(GetDevice()->Kind()))
4223 state
.mTrackEnabled
= aEnable
;
4225 if (state
.mStopped
) {
4226 // Device terminally stopped. Updating device state is pointless.
4230 if (state
.mOperationInProgress
) {
4231 // If a timer is in progress, it needs to be canceled now so the next
4232 // DisableTrack() gets a fresh start. Canceling will trigger another
4234 state
.mDisableTimer
->Cancel();
4238 if (state
.mDeviceEnabled
== aEnable
) {
4239 // Device is already in the desired state.
4243 // All paths from here on must end in setting
4244 // `state.mOperationInProgress` to false.
4245 state
.mOperationInProgress
= true;
4247 RefPtr
<MediaTimerPromise
> timerPromise
;
4249 timerPromise
= MediaTimerPromise::CreateAndResolve(true, __func__
);
4250 state
.mTrackEnabledTime
= TimeStamp::Now();
4252 const TimeDuration maxDelay
=
4253 TimeDuration::FromMilliseconds(Preferences::GetUint(
4254 GetDevice()->Kind() == MediaDeviceKind::Audioinput
4255 ? "media.getusermedia.microphone.off_while_disabled.delay_ms"
4256 : "media.getusermedia.camera.off_while_disabled.delay_ms",
4258 const TimeDuration durationEnabled
=
4259 TimeStamp::Now() - state
.mTrackEnabledTime
;
4260 const TimeDuration delay
= TimeDuration::Max(
4261 TimeDuration::FromMilliseconds(0), maxDelay
- durationEnabled
);
4262 timerPromise
= state
.mDisableTimer
->WaitFor(delay
, __func__
);
4265 RefPtr
<DeviceListener
> self
= this;
4268 GetMainThreadSerialEventTarget(), __func__
,
4269 [self
, this, &state
, aEnable
]() mutable {
4270 MOZ_ASSERT(state
.mDeviceEnabled
!= aEnable
,
4271 "Device operation hasn't started");
4272 MOZ_ASSERT(state
.mOperationInProgress
,
4273 "It's our responsibility to reset the inProgress state");
4275 LOG("DeviceListener %p %s %s device - starting device operation",
4276 this, aEnable
? "enabling" : "disabling",
4278 dom::MediaDeviceKindValues::GetString(GetDevice()->Kind()))
4281 if (state
.mStopped
) {
4282 // Source was stopped between timer resolving and this runnable.
4283 return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT
,
4287 state
.mDeviceEnabled
= aEnable
;
4289 if (mWindowListener
) {
4290 mWindowListener
->ChromeAffectingStateChanged();
4292 if (!state
.mOffWhileDisabled
|| state
.mDeviceMuted
) {
4293 // If the feature to turn a device off while disabled is itself
4294 // disabled, or the device is currently user agent muted, then
4295 // we shortcut the device operation and tell the
4296 // ux-updating code that everything went fine.
4297 return DeviceOperationPromise::CreateAndResolve(NS_OK
, __func__
);
4299 return UpdateDevice(aEnable
);
4302 // Timer was canceled by us. We signal this with NS_ERROR_ABORT.
4303 return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT
,
4307 GetMainThreadSerialEventTarget(), __func__
,
4308 [self
, this, &state
, aEnable
](nsresult aResult
) mutable {
4309 MOZ_ASSERT_IF(aResult
!= NS_ERROR_ABORT
,
4310 state
.mDeviceEnabled
== aEnable
);
4311 MOZ_ASSERT(state
.mOperationInProgress
);
4312 state
.mOperationInProgress
= false;
4314 if (state
.mStopped
) {
4315 // Device was stopped on main thread during the operation.
4320 if (NS_FAILED(aResult
) && aResult
!= NS_ERROR_ABORT
&& !aEnable
) {
4321 // To keep our internal state sane in this case, we disallow
4322 // future stops due to disable.
4323 state
.mOffWhileDisabled
= false;
4327 // This path is for a device operation aResult that was success or
4328 // NS_ERROR_ABORT (*we* canceled the operation).
4329 // At this point we have to follow up on the intended state, i.e.,
4330 // update the device state if the track state changed in the
4333 if (state
.mTrackEnabled
!= state
.mDeviceEnabled
) {
4334 // Track state changed during this operation. We'll start over.
4335 SetDeviceEnabled(state
.mTrackEnabled
);
4338 []() { MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject"); });
4341 void DeviceListener::SetDeviceMuted(bool aMute
) {
4342 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4343 MOZ_ASSERT(Activated(), "No device to set muted state for");
4345 DeviceState
& state
= *mDeviceState
;
4347 LOG("DeviceListener %p %s %s device", this, aMute
? "muting" : "unmuting",
4348 nsCString(dom::MediaDeviceKindValues::GetString(GetDevice()->Kind()))
4351 if (state
.mStopped
) {
4352 // Device terminally stopped. Updating device state is pointless.
4356 if (state
.mDeviceMuted
== aMute
) {
4357 // Device is already in the desired state.
4361 LOG("DeviceListener %p %s %s device - starting device operation", this,
4362 aMute
? "muting" : "unmuting",
4363 nsCString(dom::MediaDeviceKindValues::GetString(GetDevice()->Kind()))
4366 state
.mDeviceMuted
= aMute
;
4368 if (mWindowListener
) {
4369 mWindowListener
->ChromeAffectingStateChanged();
4371 // Update trackSource to fire mute/unmute events on all its tracks
4373 state
.mTrackSource
->Mute();
4375 state
.mTrackSource
->Unmute();
4377 if (!state
.mOffWhileDisabled
|| !state
.mDeviceEnabled
) {
4378 // If the pref to turn the underlying device is itself off, or the device
4379 // is already off, it's unecessary to do anything else.
4382 UpdateDevice(!aMute
);
4385 void DeviceListener::MuteOrUnmuteCamera(bool aMute
) {
4386 MOZ_ASSERT(NS_IsMainThread());
4392 MOZ_RELEASE_ASSERT(mWindowListener
);
4393 LOG("DeviceListener %p MuteOrUnmuteCamera: %s", this,
4394 aMute
? "mute" : "unmute");
4397 (GetDevice()->GetMediaSource() == MediaSourceEnum::Camera
)) {
4398 SetDeviceMuted(aMute
);
4402 void DeviceListener::MuteOrUnmuteMicrophone(bool aMute
) {
4403 MOZ_ASSERT(NS_IsMainThread());
4409 MOZ_RELEASE_ASSERT(mWindowListener
);
4410 LOG("DeviceListener %p MuteOrUnmuteMicrophone: %s", this,
4411 aMute
? "mute" : "unmute");
4414 (GetDevice()->GetMediaSource() == MediaSourceEnum::Microphone
)) {
4415 SetDeviceMuted(aMute
);
4419 bool DeviceListener::CapturingVideo() const {
4420 MOZ_ASSERT(NS_IsMainThread());
4421 return Activated() && mDeviceState
&& !mDeviceState
->mStopped
&&
4422 MediaEngineSource::IsVideo(GetDevice()->GetMediaSource()) &&
4423 (!GetDevice()->IsFake() ||
4424 Preferences::GetBool("media.navigator.permission.fake"));
4427 bool DeviceListener::CapturingAudio() const {
4428 MOZ_ASSERT(NS_IsMainThread());
4429 return Activated() && mDeviceState
&& !mDeviceState
->mStopped
&&
4430 MediaEngineSource::IsAudio(GetDevice()->GetMediaSource()) &&
4431 (!GetDevice()->IsFake() ||
4432 Preferences::GetBool("media.navigator.permission.fake"));
4435 CaptureState
DeviceListener::CapturingSource(MediaSourceEnum aSource
) const {
4436 MOZ_ASSERT(NS_IsMainThread());
4437 if (GetDevice()->GetMediaSource() != aSource
) {
4438 // This DeviceListener doesn't capture a matching source
4439 return CaptureState::Off
;
4442 if (mDeviceState
->mStopped
) {
4443 // The source is a match but has been permanently stopped
4444 return CaptureState::Off
;
4447 if ((aSource
== MediaSourceEnum::Camera
||
4448 aSource
== MediaSourceEnum::Microphone
) &&
4449 GetDevice()->IsFake() &&
4450 !Preferences::GetBool("media.navigator.permission.fake")) {
4451 // Fake Camera and Microphone only count if there is no fake permission
4452 return CaptureState::Off
;
4455 // Source is a match and is active and unmuted
4457 if (mDeviceState
->mDeviceEnabled
&& !mDeviceState
->mDeviceMuted
) {
4458 return CaptureState::Enabled
;
4461 return CaptureState::Disabled
;
4464 RefPtr
<DeviceListener::DeviceListenerPromise
> DeviceListener::ApplyConstraints(
4465 const MediaTrackConstraints
& aConstraints
, CallerType aCallerType
) {
4466 MOZ_ASSERT(NS_IsMainThread());
4468 if (mStopped
|| mDeviceState
->mStopped
) {
4469 LOG("DeviceListener %p %s device applyConstraints, but device is stopped",
4471 nsCString(dom::MediaDeviceKindValues::GetString(GetDevice()->Kind()))
4473 return DeviceListenerPromise::CreateAndResolve(false, __func__
);
4476 MediaManager
* mgr
= MediaManager::GetIfExists();
4478 return DeviceListenerPromise::CreateAndResolve(false, __func__
);
4481 return MediaManager::Dispatch
<DeviceListenerPromise
>(
4482 __func__
, [device
= mDeviceState
->mDevice
, aConstraints
, aCallerType
](
4483 MozPromiseHolder
<DeviceListenerPromise
>& aHolder
) mutable {
4484 MOZ_ASSERT(MediaManager::IsInMediaThread());
4485 MediaManager
* mgr
= MediaManager::GetIfExists();
4486 MOZ_RELEASE_ASSERT(mgr
); // Must exist while media thread is alive
4487 const char* badConstraint
= nullptr;
4489 device
->Reconfigure(aConstraints
, mgr
->mPrefs
, &badConstraint
);
4490 if (NS_FAILED(rv
)) {
4491 if (rv
== NS_ERROR_INVALID_ARG
) {
4492 // Reconfigure failed due to constraints
4493 if (!badConstraint
) {
4494 nsTArray
<RefPtr
<LocalMediaDevice
>> devices
;
4495 devices
.AppendElement(device
);
4496 badConstraint
= MediaConstraintsHelper::SelectSettings(
4497 NormalizedConstraints(aConstraints
), devices
, aCallerType
);
4500 // Unexpected. ApplyConstraints* cannot fail with any other error.
4502 LOG("ApplyConstraints-Task: Unexpected fail %" PRIx32
,
4503 static_cast<uint32_t>(rv
));
4506 aHolder
.Reject(MakeRefPtr
<MediaMgrError
>(
4507 MediaMgrError::Name::OverconstrainedError
, "",
4508 NS_ConvertASCIItoUTF16(badConstraint
)),
4512 // Reconfigure was successful
4513 aHolder
.Resolve(false, __func__
);
4517 PrincipalHandle
DeviceListener::GetPrincipalHandle() const {
4518 return mPrincipalHandle
;
4521 void GetUserMediaWindowListener::StopSharing() {
4522 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4524 for (auto& l
: mActiveListeners
.Clone()) {
4525 MediaSourceEnum source
= l
->GetDevice()->GetMediaSource();
4526 if (source
== MediaSourceEnum::Screen
||
4527 source
== MediaSourceEnum::Window
||
4528 source
== MediaSourceEnum::AudioCapture
) {
4534 void GetUserMediaWindowListener::StopRawID(const nsString
& removedDeviceID
) {
4535 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4537 for (auto& l
: mActiveListeners
.Clone()) {
4538 if (removedDeviceID
.Equals(l
->GetDevice()->RawID())) {
4544 void GetUserMediaWindowListener::MuteOrUnmuteCameras(bool aMute
) {
4545 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4547 if (mCamerasAreMuted
== aMute
) {
4550 mCamerasAreMuted
= aMute
;
4552 for (auto& l
: mActiveListeners
) {
4553 if (l
->GetDevice()->Kind() == MediaDeviceKind::Videoinput
) {
4554 l
->MuteOrUnmuteCamera(aMute
);
4559 void GetUserMediaWindowListener::MuteOrUnmuteMicrophones(bool aMute
) {
4560 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4562 if (mMicrophonesAreMuted
== aMute
) {
4565 mMicrophonesAreMuted
= aMute
;
4567 for (auto& l
: mActiveListeners
) {
4568 if (l
->GetDevice()->Kind() == MediaDeviceKind::Audioinput
) {
4569 l
->MuteOrUnmuteMicrophone(aMute
);
4574 void GetUserMediaWindowListener::ChromeAffectingStateChanged() {
4575 MOZ_ASSERT(NS_IsMainThread());
4577 // We wait until stable state before notifying chrome so chrome only does
4578 // one update if more updates happen in this event loop.
4580 if (mChromeNotificationTaskPosted
) {
4584 nsCOMPtr
<nsIRunnable
> runnable
=
4585 NewRunnableMethod("GetUserMediaWindowListener::NotifyChrome", this,
4586 &GetUserMediaWindowListener::NotifyChrome
);
4587 nsContentUtils::RunInStableState(runnable
.forget());
4588 mChromeNotificationTaskPosted
= true;
4591 void GetUserMediaWindowListener::NotifyChrome() {
4592 MOZ_ASSERT(mChromeNotificationTaskPosted
);
4593 mChromeNotificationTaskPosted
= false;
4595 NS_DispatchToMainThread(NS_NewRunnableFunction(
4596 "MediaManager::NotifyChrome", [windowID
= mWindowID
]() {
4597 auto* window
= nsGlobalWindowInner::GetInnerWindowWithId(windowID
);
4599 MOZ_ASSERT_UNREACHABLE("Should have window");
4603 nsresult rv
= MediaManager::NotifyRecordingStatusChange(window
);
4604 if (NS_FAILED(rv
)) {
4605 MOZ_ASSERT_UNREACHABLE("Should be able to notify chrome");
4613 } // namespace mozilla