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