/* 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 "MediaEngine.h" #include "MediaEnginePrefs.h" #include "MediaEventSource.h" #include "mozilla/dom/GetUserMediaRequest.h" #include "mozilla/Unused.h" #include "nsIMediaManager.h" #include "nsHashKeys.h" #include "nsClassHashtable.h" #include "nsRefPtrHashtable.h" #include "nsIObserver.h" #include "nsIDOMNavigatorUserMedia.h" #include "nsXULAppAPI.h" #include "mozilla/Attributes.h" #include "mozilla/Preferences.h" #include "mozilla/StaticMutex.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" #ifdef MOZ_WEBRTC # include "transport/runnable_utils.h" #endif class nsIPrefBranch; namespace mozilla { class TaskQueue; class MediaTimer; namespace dom { struct MediaStreamConstraints; struct MediaTrackConstraints; struct MediaTrackConstraintSet; enum class CallerType : uint32_t; enum class MediaDeviceKind : uint8_t; } // namespace dom namespace ipc { class PrincipalInfo; } class GetUserMediaTask; class GetUserMediaWindowListener; class MediaManager; class SourceListener; class MediaDevice : public nsIMediaDevice { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIMEDIADEVICE MediaDevice(const RefPtr& aSource, const nsString& aName, const nsString& aID, const nsString& aGroupID, const nsString& aRawID); MediaDevice(const RefPtr& aAudioDeviceInfo, const nsString& aID, const nsString& aGroupID, const nsString& aRawID = u""_ns); MediaDevice(const RefPtr& aOther, const nsString& aID, const nsString& aGroupID, const nsString& aRawID, const nsString& aRawGroupID); MediaDevice(const RefPtr& aOther, const nsString& aID, const nsString& aGroupID, const nsString& aRawID, const nsString& aRawGroupID, const nsString& aName); uint32_t GetBestFitnessDistance( const nsTArray& aConstraintSets, bool aIsChrome); nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, uint64_t aWindowId, const char** aOutBadConstraint); void SetTrack(const RefPtr& aTrack, const PrincipalHandle& 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) const; dom::MediaSourceEnum GetMediaSource() const; protected: virtual ~MediaDevice() = default; static uint32_t FitnessDistance( nsString aN, const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint); private: static bool StringsContain(const dom::OwningStringOrStringSequence& aStrings, nsString aN); static uint32_t FitnessDistance( nsString aN, const dom::ConstrainDOMStringParameters& aParams); public: const RefPtr mSource; const RefPtr mSinkInfo; const dom::MediaDeviceKind mKind; const bool mScary; const bool mIsFake; const nsString mType; const nsString mName; const nsString mID; const nsString mGroupID; const nsString mRawID; const nsString mRawGroupID; const nsString mRawName; }; typedef nsRefPtrHashtable WindowTable; typedef MozPromise, nsresult, true> SinkInfoPromise; class MediaManager final : public nsIMediaManagerService, public nsIObserver { friend SourceListener; 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_NSIMEDIAMANAGERSERVICE media::Parent* GetNonE10sParent(); MediaEngine* GetBackend(); 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(); } typedef dom::NavigatorUserMediaSuccessCallback GetUserMediaSuccessCallback; typedef dom::NavigatorUserMediaErrorCallback GetUserMediaErrorCallback; MOZ_CAN_RUN_SCRIPT static void CallOnError(GetUserMediaErrorCallback& aCallback, dom::MediaStreamError& aError); MOZ_CAN_RUN_SCRIPT static void CallOnSuccess(GetUserMediaSuccessCallback& aCallback, DOMMediaStream& aTrack); typedef nsTArray> MediaDeviceSet; typedef media::Refcountable MediaDeviceSetRefCnt; typedef MozPromise, RefPtr, true> StreamPromise; typedef MozPromise, RefPtr, true> DevicesPromise; typedef MozPromise, true> MgrPromise; typedef MozPromise, true> BadConstraintsPromise; RefPtr GetUserMedia( nsPIDOMWindowInner* aWindow, const dom::MediaStreamConstraints& aConstraints, dom::CallerType aCallerType); MOZ_CAN_RUN_SCRIPT nsresult GetUserMediaDevices( nsPIDOMWindowInner* aWindow, const dom::MediaStreamConstraints& aConstraints, dom::MozGetUserMediaDevicesSuccessCallback& aOnSuccess, uint64_t aInnerWindowID = 0, const nsAString& aCallID = nsString()); RefPtr EnumerateDevices(nsPIDOMWindowInner* aWindow, dom::CallerType aCallerType); nsresult EnumerateDevices(nsPIDOMWindowInner* aWindow, dom::Promise& aPromise); // Get the sink that corresponds to the given device id. // It is resposible to check if an application is // authorized to play audio through the requested device. // The returned promise will be resolved with the device // information if the device id matches one and operation is // allowed. The default device is always allowed. Non default // devices are allowed only in secure context. It is pending to // implement an user authorization model. The promise will be // rejected in the following cases: // NS_ERROR_NOT_AVAILABLE: Device id does not exist. // NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR: // The requested device exists but it is not allowed to be used. // Currently, this happens only on non-default default devices // and non https connections. TODO, authorization model to allow // an application to play audio through the device (Bug 1493982). // NS_ERROR_ABORT: General error. RefPtr GetSinkDevice(nsPIDOMWindowInner* aWindow, const nsString& aDeviceId); void OnNavigation(uint64_t aWindowID); void OnCameraMute(bool aMute); void OnMicrophoneMute(bool aMute); bool IsActivelyCapturingOrHasAPermission(uint64_t aWindowId); MediaEventSource& DeviceListChangeEvent() { return mDeviceListChangeEvent; } MediaEnginePrefs mPrefs; private: static nsresult GenerateUUID(nsAString& aResult); static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey); public: // TODO: make private once we upgrade to GCC 4.8+ on linux. static void AnonymizeDevices(MediaDeviceSet& aDevices, const nsACString& aOriginKey, const uint64_t aWindowId); static already_AddRefed ToJSArray( MediaDeviceSet& aDevices); /** * 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 DeviceEnumerationType : uint8_t { Normal, // Enumeration should not return loopback or fake devices Fake, // Enumeration should return fake device(s) Loopback /* Enumeration should return loopback device(s) (possibly in addition to normal devices) */ }; RefPtr EnumerateRawDevices( uint64_t aWindowId, dom::MediaSourceEnum aVideoInputType, dom::MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType, DeviceEnumerationType aVideoInputEnumType, DeviceEnumerationType aAudioInputEnumType, bool aForceNoPermRequest, const RefPtr& aOutDevices); RefPtr EnumerateDevicesImpl( uint64_t aWindowId, dom::MediaSourceEnum aVideoInputType, dom::MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType, DeviceEnumerationType aVideoInputEnumType, DeviceEnumerationType aAudioInputEnumType, bool aForceNoPermRequest, const RefPtr& aOutDevices); RefPtr SelectSettings( const dom::MediaStreamConstraints& aConstraints, bool aIsChrome, const RefPtr& aSources); 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(); // ONLY access from MainThread so we don't need to lock WindowTable mActiveWindows; nsRefPtrHashtable mActiveCallbacks; nsClassHashtable> mCallIds; nsTArray> mPendingGUMRequest; RefPtr mDeviceChangeTimer; bool mCamerasMuted = false; bool mMicrophonesMuted = false; // Always exists const RefPtr mMediaThread; nsCOMPtr mShutdownBlocker; // ONLY accessed from MediaManagerThread RefPtr mBackend; static StaticRefPtr sSingleton; static StaticMutex sSingletonMutex; struct nsStringHasher { using Key = nsString; using Lookup = nsString; static HashNumber hash(const Lookup& aLookup) { return HashString(aLookup.get()); } static bool match(const Key& aKey, const Lookup& aLookup) { return aKey == aLookup; } }; using DeviceIdSet = HashSet; DeviceIdSet mDeviceIDs; // Connect/Disconnect on media thread only MediaEventListener mDeviceListChangeListener; MediaEventProducer mDeviceListChangeEvent; public: RefPtr> mNonE10sParent; }; } // namespace mozilla #endif // MOZILLA_MEDIAMANAGER_H