/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MOZILLA_MEDIAMANAGER_H #define MOZILLA_MEDIAMANAGER_H #include "MediaEnginePrefs.h" #include "MediaEventSource.h" #include "mozilla/dom/GetUserMediaRequest.h" #include "mozilla/Unused.h" #include "nsIMediaDevice.h" #include "nsIMediaManager.h" #include "nsHashKeys.h" #include "nsClassHashtable.h" #include "nsRefPtrHashtable.h" #include "nsIMemoryReporter.h" #include "nsIObserver.h" #include "nsXULAppAPI.h" #include "mozilla/Attributes.h" #include "mozilla/Preferences.h" #include "mozilla/StaticPtr.h" #include "mozilla/dom/MediaStreamBinding.h" #include "mozilla/dom/MediaStreamTrackBinding.h" #include "mozilla/dom/MediaStreamError.h" #include "mozilla/dom/NavigatorBinding.h" #include "mozilla/media/MediaChild.h" #include "mozilla/media/MediaParent.h" #include "mozilla/Logging.h" #include "mozilla/UniquePtr.h" #include "DOMMediaStream.h" #include "PerformanceRecorder.h" #ifdef MOZ_WEBRTC # include "transport/runnable_utils.h" #endif class AudioDeviceInfo; class nsIPrefBranch; namespace mozilla { class MediaEngine; class MediaEngineSource; class TaskQueue; class MediaTimer; class MediaTrack; namespace dom { struct AudioOutputOptions; struct MediaStreamConstraints; struct MediaTrackConstraints; struct MediaTrackConstraintSet; struct MediaTrackSettings; enum class CallerType : uint32_t; enum class MediaDeviceKind : uint8_t; } // namespace dom namespace ipc { class PrincipalInfo; } class GetUserMediaTask; class GetUserMediaWindowListener; class MediaManager; class DeviceListener; /** * Device info that is independent of any Window. * MediaDevices can be shared, unlike LocalMediaDevices. */ class MediaDevice final { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDevice) /** * Whether source device does end-run around cross origin restrictions. */ enum class IsScary { No, Yes }; MediaDevice(MediaEngine* aEngine, dom::MediaSourceEnum aMediaSource, const nsString& aRawName, const nsString& aRawID, const nsString& aRawGroupID, IsScary aIsScary); MediaDevice(MediaEngine* aEngine, const RefPtr& aAudioDeviceInfo, const nsString& aRawID); static RefPtr CopyWithNewRawGroupId( const RefPtr& aOther, const nsString& aRawGroupID); dom::MediaSourceEnum GetMediaSource() const; protected: ~MediaDevice(); public: const RefPtr mEngine; const RefPtr mAudioDeviceInfo; const dom::MediaSourceEnum mMediaSource; const dom::MediaDeviceKind mKind; const bool mScary; const bool mIsFake; const nsString mType; const nsString mRawID; const nsString mRawGroupID; const nsString mRawName; }; /** * Device info that is specific to a particular Window. If the device is a * source device, then a single corresponding MediaEngineSource is provided, * which can provide a maximum of one capture stream. LocalMediaDevices are * not shared, but APIs returning LocalMediaDevices return a new object each * call. */ class LocalMediaDevice final : public nsIMediaDevice { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIMEDIADEVICE LocalMediaDevice(RefPtr aRawDevice, const nsString& aID, const nsString& aGroupID, const nsString& aName); uint32_t GetBestFitnessDistance( const nsTArray& aConstraintSets, dom::CallerType aCallerType); nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, uint64_t aWindowId, const char** aOutBadConstraint); void SetTrack(const RefPtr& aTrack, const nsMainThreadPtrHandle& aPrincipal); nsresult Start(); nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, const char** aOutBadConstraint); nsresult FocusOnSelectedSource(); nsresult Stop(); nsresult Deallocate(); void GetSettings(dom::MediaTrackSettings& aOutSettings); MediaEngineSource* Source(); const TrackingId& GetTrackingId() const; // Returns null if not a physical audio device. AudioDeviceInfo* GetAudioDeviceInfo() const { return mRawDevice->mAudioDeviceInfo; } dom::MediaSourceEnum GetMediaSource() const { return mRawDevice->GetMediaSource(); } dom::MediaDeviceKind Kind() const { return mRawDevice->mKind; } bool IsFake() const { return mRawDevice->mIsFake; } const nsString& RawID() { return mRawDevice->mRawID; } private: virtual ~LocalMediaDevice() = default; static uint32_t FitnessDistance( nsString aN, const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint); static bool StringsContain(const dom::OwningStringOrStringSequence& aStrings, nsString aN); static uint32_t FitnessDistance( nsString aN, const dom::ConstrainDOMStringParameters& aParams); public: const RefPtr mRawDevice; const nsString mName; const nsString mID; const nsString mGroupID; private: RefPtr mSource; }; typedef nsRefPtrHashtable WindowTable; class MediaManager final : public nsIMediaManagerService, public nsIMemoryReporter, public nsIObserver { friend DeviceListener; public: static already_AddRefed GetInstance(); // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread // from MediaManager thread. static MediaManager* Get(); static MediaManager* GetIfExists(); static void StartupInit(); static void Dispatch(already_AddRefed task); /** * Posts an async operation to the media manager thread. * FunctionType must be a function that takes a `MozPromiseHolder&`. * * The returned promise is resolved or rejected by aFunction on the media * manager thread. */ template static RefPtr Dispatch(const char* aName, FunctionType&& aFunction); #ifdef DEBUG static bool IsInMediaThread(); #endif static bool Exists() { return !!GetIfExists(); } static nsresult NotifyRecordingStatusChange(nsPIDOMWindowInner* aWindow); NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIOBSERVER NS_DECL_NSIMEMORYREPORTER NS_DECL_NSIMEDIAMANAGERSERVICE media::Parent* GetNonE10sParent(); // If the window has not been destroyed, then return the // GetUserMediaWindowListener for this window. // If the window has been destroyed, then return null. RefPtr GetOrMakeWindowListener( nsPIDOMWindowInner* aWindow); WindowTable* GetActiveWindows() { MOZ_ASSERT(NS_IsMainThread()); return &mActiveWindows; } GetUserMediaWindowListener* GetWindowListener(uint64_t aWindowId) { MOZ_ASSERT(NS_IsMainThread()); return mActiveWindows.GetWeak(aWindowId); } void AddWindowID(uint64_t aWindowId, RefPtr aListener); void RemoveWindowID(uint64_t aWindowId); void SendPendingGUMRequest(); bool IsWindowStillActive(uint64_t aWindowId) { return !!GetWindowListener(aWindowId); } bool IsWindowListenerStillActive( const RefPtr& aListener); static bool IsOn(const dom::OwningBooleanOrMediaTrackConstraints& aUnion) { return !aUnion.IsBoolean() || aUnion.GetAsBoolean(); } using GetUserMediaSuccessCallback = dom::NavigatorUserMediaSuccessCallback; using GetUserMediaErrorCallback = dom::NavigatorUserMediaErrorCallback; MOZ_CAN_RUN_SCRIPT static void CallOnError(GetUserMediaErrorCallback& aCallback, dom::MediaStreamError& aError); MOZ_CAN_RUN_SCRIPT static void CallOnSuccess(GetUserMediaSuccessCallback& aCallback, DOMMediaStream& aTrack); using MediaDeviceSet = nsTArray>; using MediaDeviceSetRefCnt = media::Refcountable; using LocalMediaDeviceSet = nsTArray>; using LocalMediaDeviceSetRefCnt = media::Refcountable; using StreamPromise = MozPromise, RefPtr, true>; using DeviceSetPromise = MozPromise, RefPtr, true>; using ConstDeviceSetPromise = MozPromise, RefPtr, true>; using LocalDevicePromise = MozPromise, RefPtr, true>; using LocalDeviceSetPromise = MozPromise, RefPtr, true>; using MgrPromise = MozPromise, true>; RefPtr GetUserMedia( nsPIDOMWindowInner* aWindow, const dom::MediaStreamConstraints& aConstraints, dom::CallerType aCallerType); RefPtr SelectAudioOutput( nsPIDOMWindowInner* aWindow, const dom::AudioOutputOptions& aOptions, dom::CallerType aCallerType); // Return the list of microphone, camera, and speaker devices. // MediaDeviceSets provided on promise resolution are shared between // callers and so cannot be modified. RefPtr GetPhysicalDevices(); void OnNavigation(uint64_t aWindowID); void OnCameraMute(bool aMute); void OnMicrophoneMute(bool aMute); bool IsActivelyCapturingOrHasAPermission(uint64_t aWindowId); MediaEventSource& DeviceListChangeEvent() { return mDeviceListChangeEvent; } RefPtr AnonymizeDevices( nsPIDOMWindowInner* aWindow, RefPtr aDevices); MediaEnginePrefs mPrefs; private: static nsresult GenerateUUID(nsAString& aResult); public: /** * This function tries to guess the group id for a video device in aDevices * based on the device name. If the name of only one audio device in aAudios * contains the name of the video device, then, this video device will take * the group id of the audio device. Since this is a guess we try to minimize * the probability of false positive. If we fail to find a correlation we * leave the video group id untouched. In that case the group id will be the * video device name. */ static void GuessVideoDeviceGroupIDs(MediaDeviceSet& aDevices, const MediaDeviceSet& aAudios); private: enum class EnumerationFlag { AllowPermissionRequest, EnumerateAudioOutputs, ForceFakes, }; using EnumerationFlags = EnumSet; RefPtr EnumerateDevicesImpl( nsPIDOMWindowInner* aWindow, dom::MediaSourceEnum aVideoInputType, dom::MediaSourceEnum aAudioInputType, EnumerationFlags aFlags); RefPtr EnumerateRawDevices( dom::MediaSourceEnum aVideoInputType, dom::MediaSourceEnum aAudioInputType, EnumerationFlags aFlags); RefPtr SelectSettings( const dom::MediaStreamConstraints& aConstraints, dom::CallerType aCallerType, RefPtr aDevices); void GetPref(nsIPrefBranch* aBranch, const char* aPref, const char* aData, int32_t* aVal); void GetPrefBool(nsIPrefBranch* aBranch, const char* aPref, const char* aData, bool* aVal); void GetPrefs(nsIPrefBranch* aBranch, const char* aData); // Make private because we want only one instance of this class explicit MediaManager(already_AddRefed aMediaThread); ~MediaManager() = default; void Shutdown(); void StopScreensharing(uint64_t aWindowID); void RemoveMediaDevicesCallback(uint64_t aWindowID); void DeviceListChanged(); void InvalidateDeviceCache(); void HandleDeviceListChanged(); // Returns the number of incomplete tasks associated with this window, // including the newly added task. size_t AddTaskAndGetCount(uint64_t aWindowID, const nsAString& aCallID, RefPtr aTask); // Finds the task corresponding to aCallID and removes it from tracking. RefPtr TakeGetUserMediaTask(const nsAString& aCallID); // Intended for use with "media.navigator.permission.disabled" to bypass the // permission prompt and use the first appropriate device. void NotifyAllowed(const nsString& aCallID, const LocalMediaDeviceSet& aDevices); // Media thread only MediaEngine* GetBackend(); MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf); // ONLY access from MainThread so we don't need to lock WindowTable mActiveWindows; nsRefPtrHashtable mActiveCallbacks; nsClassHashtable> mCallIds; nsTArray> mPendingGUMRequest; // non-null if a device enumeration is in progress and was started after the // last device-change invalidation RefPtr>>> mPendingDevicesPromises; RefPtr mPhysicalDevices; TimeStamp mUnhandledDeviceChangeTime; RefPtr mDeviceChangeTimer; bool mCamerasMuted = false; bool mMicrophonesMuted = false; // Always exists const RefPtr mMediaThread; nsCOMPtr mShutdownBlocker; // ONLY accessed from MediaManagerThread RefPtr mBackend; // Accessed only on main thread and mMediaThread. // Set before mMediaThread is created, and cleared on main thread after last // mMediaThread task is run. static StaticRefPtr sSingleton; // Connect/Disconnect on media thread only MediaEventListener mDeviceListChangeListener; MediaEventProducer mDeviceListChangeEvent; public: RefPtr> mNonE10sParent; }; } // namespace mozilla #endif // MOZILLA_MEDIAMANAGER_H