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