dom/media/MediaManager.h
author Timothy Guan-tin Chien <timdream@gmail.com>
Mon, 07 Jan 2019 16:00:55 +0000
changeset 506594 2046aa36055220d9187f968fca3ab070d2de7665
parent 506547 59f2b329c3a1a189cb830443c0410e3bf54bec44
permissions -rw-r--r--
Bug 1516292 - Use isSameNode() to compare mozFullScreenElement and the video host element. r=edgar, a=RyanVM This does what's done in bug 1505547 but in a different way. For some reason, access shadowRoot.host in the function can trigger an assertion. this.video is a Proxy so it cannot be compared, but all its methods are proxied. Differential Revision: https://phabricator.services.mozilla.com/D15767

/* 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 "mozilla/media/DeviceChangeCallback.h"
#include "mozilla/dom/GetUserMediaRequest.h"
#include "mozilla/Unused.h"
#include "nsAutoPtr.h"
#include "nsIMediaManager.h"

#include "nsHashKeys.h"
#include "nsClassHashtable.h"
#include "nsRefPtrHashtable.h"
#include "nsIObserver.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"

#include "nsIDOMNavigatorUserMedia.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"

#ifdef MOZ_WEBRTC
#include "mtransport/runnable_utils.h"
#endif

// Note, these suck in Windows headers, unfortunately.
#include "base/thread.h"
#include "base/task.h"

namespace mozilla {
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 AllocationHandle;
class GetUserMediaTask;
class GetUserMediaWindowListener;
class MediaManager;
class SourceListener;

LogModule* GetMediaManagerLog();

class MediaDevice : public nsIMediaDevice {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIMEDIADEVICE

  explicit MediaDevice(const RefPtr<MediaEngineSource>& aSource,
                       const nsString& aName, const nsString& aID,
                       const nsString& aRawID);

  explicit MediaDevice(const RefPtr<AudioDeviceInfo>& aAudioDeviceInfo,
                       const nsString& aID,
                       const nsString& aRawID = NS_LITERAL_STRING(""));

  explicit MediaDevice(const RefPtr<MediaDevice>& aOther, const nsString& aID,
                       const nsString& aRawID);

  uint32_t GetBestFitnessDistance(
      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
      bool aIsChrome);

  nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                    const MediaEnginePrefs& aPrefs,
                    const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
                    const char** aOutBadConstraint);
  void SetTrack(const RefPtr<SourceMediaStream>& aStream, TrackID aTrackID,
                const PrincipalHandle& aPrincipal);
  nsresult Start();
  nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints,
                       const MediaEnginePrefs& aPrefs,
                       const char** aOutBadConstraint);
  nsresult FocusOnSelectedSource();
  nsresult Stop();
  nsresult Deallocate();

  void Pull(const RefPtr<SourceMediaStream>& aStream, TrackID aTrackID,
            StreamTime aEndOfAppendedData, StreamTime aDesiredTime,
            const PrincipalHandle& aPrincipal);

  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);

  // Assigned on allocation on media thread, then read on the media thread and
  // graph thread
  RefPtr<AllocationHandle> mAllocationHandle;

 public:
  const RefPtr<MediaEngineSource> mSource;
  const RefPtr<AudioDeviceInfo> mSinkInfo;
  const dom::MediaDeviceKind mKind;
  const bool mScary;
  const nsString mType;
  const nsString mName;
  const nsString mID;
  const nsString mRawID;
};

typedef nsRefPtrHashtable<nsUint64HashKey, GetUserMediaWindowListener>
    WindowTable;
typedef MozPromise<RefPtr<AudioDeviceInfo>, nsresult, true> SinkInfoPromise;

class MediaManager final : public nsIMediaManagerService,
                           public nsIObserver,
                           public DeviceChangeCallback {
  friend SourceListener;

 public:
  static already_AddRefed<MediaManager> 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 PostTask(already_AddRefed<Runnable> 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 <typename MozPromiseType, typename FunctionType>
  static RefPtr<MozPromiseType> PostTask(const char* aName,
                                         FunctionType&& aFunction);

#ifdef DEBUG
  static bool IsInMediaThread();
#endif

  static bool Exists() { return !!sSingleton; }

  static nsresult NotifyRecordingStatusChange(nsPIDOMWindowInner* aWindow);

  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIOBSERVER
  NS_DECL_NSIMEDIAMANAGERSERVICE

  media::Parent<media::NonE10s>* 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, GetUserMediaWindowListener* aListener);
  void RemoveWindowID(uint64_t aWindowId);
  void SendPendingGUMRequest();
  bool IsWindowStillActive(uint64_t aWindowId) {
    return !!GetWindowListener(aWindowId);
  }
  bool IsWindowListenerStillActive(GetUserMediaWindowListener* aListener);
  // Note: also calls aListener->Remove(), even if inactive
  void RemoveFromWindowList(uint64_t aWindowID,
                            GetUserMediaWindowListener* aListener);

  typedef dom::CallbackObjectHolder<dom::NavigatorUserMediaSuccessCallback,
                                    nsIDOMGetUserMediaSuccessCallback>
      GetUserMediaSuccessCallback;
  typedef dom::CallbackObjectHolder<dom::NavigatorUserMediaErrorCallback,
                                    nsIDOMGetUserMediaErrorCallback>
      GetUserMediaErrorCallback;

  static void CallOnError(const GetUserMediaErrorCallback* aCallback,
                          dom::MediaStreamError& aError);
  static void CallOnSuccess(const GetUserMediaSuccessCallback* aCallback,
                            DOMMediaStream& aStream);

  typedef nsTArray<RefPtr<MediaDevice>> MediaDeviceSet;
  typedef media::Refcountable<MediaDeviceSet> MediaDeviceSetRefCnt;

  typedef MozPromise<RefPtr<DOMMediaStream>, RefPtr<MediaMgrError>, true>
      StreamPromise;
  typedef MozPromise<RefPtr<MediaDeviceSetRefCnt>, RefPtr<MediaMgrError>, true>
      MediaDeviceSetPromise;
  typedef MozPromise<const char*, nsresult, false> BadConstraintsPromise;

  RefPtr<StreamPromise> GetUserMedia(
      nsPIDOMWindowInner* aWindow,
      const dom::MediaStreamConstraints& aConstraints,
      dom::CallerType aCallerType);

  nsresult GetUserMediaDevices(
      nsPIDOMWindowInner* aWindow,
      const dom::MediaStreamConstraints& aConstraints,
      dom::MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
      uint64_t aInnerWindowID = 0, const nsAString& aCallID = nsString());
  RefPtr<MediaDeviceSetPromise> 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<SinkInfoPromise> GetSinkDevice(nsPIDOMWindowInner* aWindow,
                                        const nsString& aDeviceId);

  void OnNavigation(uint64_t aWindowID);
  bool IsActivelyCapturingOrHasAPermission(uint64_t aWindowId);

  MediaEnginePrefs mPrefs;

  virtual int AddDeviceChangeCallback(DeviceChangeCallback* aCallback) override;
  virtual void OnDeviceChange() override;

 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);
  static already_AddRefed<nsIWritableVariant> ToJSArray(
      MediaDeviceSet& aDevices);

 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<MediaDeviceSetPromise> EnumerateRawDevices(
      uint64_t aWindowId, dom::MediaSourceEnum aVideoInputType,
      dom::MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType,
      DeviceEnumerationType aVideoInputEnumType = DeviceEnumerationType::Normal,
      DeviceEnumerationType aAudioInputEnumType =
          DeviceEnumerationType::Normal);

  RefPtr<MediaDeviceSetPromise> EnumerateDevicesImpl(
      uint64_t aWindowId, dom::MediaSourceEnum aVideoInputType,
      dom::MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType,
      DeviceEnumerationType aVideoInputEnumType,
      DeviceEnumerationType aAudioInputEnumType);

  RefPtr<BadConstraintsPromise> SelectSettings(
      const dom::MediaStreamConstraints& aConstraints, bool aIsChrome,
      const RefPtr<MediaDeviceSetRefCnt>& 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
  MediaManager();

  ~MediaManager() {}
  void Shutdown();

  void StopScreensharing(uint64_t aWindowID);

  /**
   * Calls aCallback with a GetUserMediaWindowListener argument once for
   * each window listener associated with aWindow and its child windows.
   */
  template <typename FunctionType>
  void IterateWindowListeners(nsPIDOMWindowInner* aWindow,
                              const FunctionType& aCallback);

  void StopMediaStreams();
  void RemoveMediaDevicesCallback(uint64_t aWindowID);

  // ONLY access from MainThread so we don't need to lock
  WindowTable mActiveWindows;
  nsRefPtrHashtable<nsStringHashKey, GetUserMediaTask> mActiveCallbacks;
  nsClassHashtable<nsUint64HashKey, nsTArray<nsString>> mCallIds;
  nsTArray<RefPtr<dom::GetUserMediaRequest>> mPendingGUMRequest;

  // Always exists
  nsAutoPtr<base::Thread> mMediaThread;
  nsCOMPtr<nsIAsyncShutdownBlocker> mShutdownBlocker;

  // ONLY accessed from MediaManagerThread
  RefPtr<MediaEngine> mBackend;

  static StaticRefPtr<MediaManager> sSingleton;

  nsTArray<nsString> mDeviceIDs;

 public:
  RefPtr<media::Parent<media::NonE10s>> mNonE10sParent;
};

}  // namespace mozilla

#endif  // MOZILLA_MEDIAMANAGER_H