dom/media/MediaManager.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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 "MediaStreamGraph.h"
    10 #include "GetUserMediaRequest.h"
    11 #include "nsHashPropertyBag.h"
    12 #ifdef MOZ_WIDGET_GONK
    13 #include "nsIAudioManager.h"
    14 #endif
    15 #include "nsIDOMFile.h"
    16 #include "nsIEventTarget.h"
    17 #include "nsIUUIDGenerator.h"
    18 #include "nsIScriptGlobalObject.h"
    19 #include "nsIPermissionManager.h"
    20 #include "nsIPopupWindowManager.h"
    21 #include "nsISupportsArray.h"
    22 #include "nsIDocShell.h"
    23 #include "nsIDocument.h"
    24 #include "nsISupportsPrimitives.h"
    25 #include "nsIInterfaceRequestorUtils.h"
    26 #include "mozilla/Types.h"
    27 #include "mozilla/dom/ContentChild.h"
    28 #include "mozilla/dom/MediaStreamBinding.h"
    29 #include "mozilla/dom/MediaStreamTrackBinding.h"
    30 #include "mozilla/dom/GetUserMediaRequestBinding.h"
    31 #include "MediaTrackConstraints.h"
    33 #include "Latency.h"
    35 // For PR_snprintf
    36 #include "prprf.h"
    38 #include "nsJSUtils.h"
    39 #include "nsDOMFile.h"
    40 #include "nsGlobalWindow.h"
    42 /* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */
    43 #include "MediaEngineDefault.h"
    44 #if defined(MOZ_WEBRTC)
    45 #include "MediaEngineWebRTC.h"
    46 #endif
    48 #ifdef MOZ_B2G
    49 #include "MediaPermissionGonk.h"
    50 #endif
    52 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
    53 // GetTickCount() and conflicts with MediaStream::GetCurrentTime.
    54 #ifdef GetCurrentTime
    55 #undef GetCurrentTime
    56 #endif
    58 namespace mozilla {
    60 #ifdef LOG
    61 #undef LOG
    62 #endif
    64 #ifdef PR_LOGGING
    65 PRLogModuleInfo*
    66 GetMediaManagerLog()
    67 {
    68   static PRLogModuleInfo *sLog;
    69   if (!sLog)
    70     sLog = PR_NewLogModule("MediaManager");
    71   return sLog;
    72 }
    73 #define LOG(msg) PR_LOG(GetMediaManagerLog(), PR_LOG_DEBUG, msg)
    74 #else
    75 #define LOG(msg)
    76 #endif
    78 using dom::MediaStreamConstraints;         // Outside API (contains JSObject)
    79 using dom::MediaTrackConstraintSet;        // Mandatory or optional constraints
    80 using dom::MediaTrackConstraints;          // Raw mMandatory (as JSObject)
    81 using dom::GetUserMediaRequest;
    82 using dom::Sequence;
    83 using dom::OwningBooleanOrMediaTrackConstraints;
    84 using dom::SupportedAudioConstraints;
    85 using dom::SupportedVideoConstraints;
    87 ErrorCallbackRunnable::ErrorCallbackRunnable(
    88   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
    89   nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
    90   const nsAString& aErrorMsg, uint64_t aWindowID)
    91   : mErrorMsg(aErrorMsg)
    92   , mWindowID(aWindowID)
    93   , mManager(MediaManager::GetInstance())
    94 {
    95   mSuccess.swap(aSuccess);
    96   mError.swap(aError);
    97 }
    99 ErrorCallbackRunnable::~ErrorCallbackRunnable()
   100 {
   101   MOZ_ASSERT(!mSuccess && !mError);
   102 }
   104 NS_IMETHODIMP
   105 ErrorCallbackRunnable::Run()
   106 {
   107   // Only run if the window is still active.
   108   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   110   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget();
   111   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
   113   if (!(mManager->IsWindowStillActive(mWindowID))) {
   114     return NS_OK;
   115   }
   116   // This is safe since we're on main-thread, and the windowlist can only
   117   // be invalidated from the main-thread (see OnNavigation)
   118   error->OnError(mErrorMsg);
   119   return NS_OK;
   120 }
   122 /**
   123  * Invoke the "onSuccess" callback in content. The callback will take a
   124  * DOMBlob in the case of {picture:true}, and a MediaStream in the case of
   125  * {audio:true} or {video:true}. There is a constructor available for each
   126  * form. Do this only on the main thread.
   127  */
   128 class SuccessCallbackRunnable : public nsRunnable
   129 {
   130 public:
   131   SuccessCallbackRunnable(
   132     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
   133     nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
   134     nsIDOMFile* aFile, uint64_t aWindowID)
   135     : mFile(aFile)
   136     , mWindowID(aWindowID)
   137     , mManager(MediaManager::GetInstance())
   138   {
   139     mSuccess.swap(aSuccess);
   140     mError.swap(aError);
   141   }
   143   NS_IMETHOD
   144   Run()
   145   {
   146     // Only run if the window is still active.
   147     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   149     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget();
   150     nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
   152     if (!(mManager->IsWindowStillActive(mWindowID))) {
   153       return NS_OK;
   154     }
   155     // This is safe since we're on main-thread, and the windowlist can only
   156     // be invalidated from the main-thread (see OnNavigation)
   157     success->OnSuccess(mFile);
   158     return NS_OK;
   159   }
   161 private:
   162   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
   163   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
   164   nsCOMPtr<nsIDOMFile> mFile;
   165   uint64_t mWindowID;
   166   nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
   167 };
   169 /**
   170  * Invoke the GetUserMediaDevices success callback. Wrapped in a runnable
   171  * so that it may be called on the main thread. The error callback is also
   172  * passed so it can be released correctly.
   173  */
   174 class DeviceSuccessCallbackRunnable: public nsRunnable
   175 {
   176 public:
   177   DeviceSuccessCallbackRunnable(
   178     uint64_t aWindowID,
   179     nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback>& aSuccess,
   180     nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
   181     nsTArray<nsCOMPtr<nsIMediaDevice> >* aDevices)
   182     : mDevices(aDevices)
   183     , mWindowID(aWindowID)
   184     , mManager(MediaManager::GetInstance())
   185   {
   186     mSuccess.swap(aSuccess);
   187     mError.swap(aError);
   188   }
   190   NS_IMETHOD
   191   Run()
   192   {
   193     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   195     // Only run if window is still on our active list.
   196     if (!mManager->IsWindowStillActive(mWindowID)) {
   197       return NS_OK;
   198     }
   200     nsCOMPtr<nsIWritableVariant> devices =
   201       do_CreateInstance("@mozilla.org/variant;1");
   203     int32_t len = mDevices->Length();
   204     if (len == 0) {
   205       // XXX
   206       // We should in the future return an empty array, and dynamically add
   207       // devices to the dropdowns if things are hotplugged while the
   208       // requester is up.
   209       mError->OnError(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
   210       return NS_OK;
   211     }
   213     nsTArray<nsIMediaDevice*> tmp(len);
   214     for (int32_t i = 0; i < len; i++) {
   215       tmp.AppendElement(mDevices->ElementAt(i));
   216     }
   218     devices->SetAsArray(nsIDataType::VTYPE_INTERFACE,
   219                         &NS_GET_IID(nsIMediaDevice),
   220                         mDevices->Length(),
   221                         const_cast<void*>(
   222                           static_cast<const void*>(tmp.Elements())
   223                         ));
   225     mSuccess->OnSuccess(devices);
   226     return NS_OK;
   227   }
   229 private:
   230   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mSuccess;
   231   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
   232   nsAutoPtr<nsTArray<nsCOMPtr<nsIMediaDevice> > > mDevices;
   233   uint64_t mWindowID;
   234   nsRefPtr<MediaManager> mManager;
   235 };
   237 // Handle removing GetUserMediaCallbackMediaStreamListener from main thread
   238 class GetUserMediaListenerRemove: public nsRunnable
   239 {
   240 public:
   241   GetUserMediaListenerRemove(uint64_t aWindowID,
   242     GetUserMediaCallbackMediaStreamListener *aListener)
   243     : mWindowID(aWindowID)
   244     , mListener(aListener) {}
   246   NS_IMETHOD
   247   Run()
   248   {
   249     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   250     nsRefPtr<MediaManager> manager(MediaManager::GetInstance());
   251     manager->RemoveFromWindowList(mWindowID, mListener);
   252     return NS_OK;
   253   }
   255 protected:
   256   uint64_t mWindowID;
   257   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   258 };
   260 /**
   261  * nsIMediaDevice implementation.
   262  */
   263 NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
   265 MediaDevice* MediaDevice::Create(MediaEngineVideoSource* source) {
   266   return new VideoDevice(source);
   267 }
   269 MediaDevice* MediaDevice::Create(MediaEngineAudioSource* source) {
   270   return new AudioDevice(source);
   271 }
   273 MediaDevice::MediaDevice(MediaEngineSource* aSource)
   274   : mHasFacingMode(false)
   275   , mSource(aSource) {
   276   mSource->GetName(mName);
   277   mSource->GetUUID(mID);
   278 }
   280 VideoDevice::VideoDevice(MediaEngineVideoSource* aSource)
   281   : MediaDevice(aSource) {
   282 #ifdef MOZ_B2G_CAMERA
   283   if (mName.EqualsLiteral("back")) {
   284     mHasFacingMode = true;
   285     mFacingMode = dom::VideoFacingModeEnum::Environment;
   286   } else if (mName.EqualsLiteral("front")) {
   287     mHasFacingMode = true;
   288     mFacingMode = dom::VideoFacingModeEnum::User;
   289   }
   290 #endif // MOZ_B2G_CAMERA
   292   // Kludge to test user-facing cameras on OSX.
   293   if (mName.Find(NS_LITERAL_STRING("Face")) != -1) {
   294     mHasFacingMode = true;
   295     mFacingMode = dom::VideoFacingModeEnum::User;
   296   }
   297 }
   299 AudioDevice::AudioDevice(MediaEngineAudioSource* aSource)
   300   : MediaDevice(aSource) {}
   302 NS_IMETHODIMP
   303 MediaDevice::GetName(nsAString& aName)
   304 {
   305   aName.Assign(mName);
   306   return NS_OK;
   307 }
   309 NS_IMETHODIMP
   310 MediaDevice::GetType(nsAString& aType)
   311 {
   312   return NS_OK;
   313 }
   315 NS_IMETHODIMP
   316 VideoDevice::GetType(nsAString& aType)
   317 {
   318   aType.Assign(NS_LITERAL_STRING("video"));
   319   return NS_OK;
   320 }
   322 NS_IMETHODIMP
   323 AudioDevice::GetType(nsAString& aType)
   324 {
   325   aType.Assign(NS_LITERAL_STRING("audio"));
   326   return NS_OK;
   327 }
   329 NS_IMETHODIMP
   330 MediaDevice::GetId(nsAString& aID)
   331 {
   332   aID.Assign(mID);
   333   return NS_OK;
   334 }
   336 NS_IMETHODIMP
   337 MediaDevice::GetFacingMode(nsAString& aFacingMode)
   338 {
   339   if (mHasFacingMode) {
   340     aFacingMode.Assign(NS_ConvertUTF8toUTF16(
   341         dom::VideoFacingModeEnumValues::strings[uint32_t(mFacingMode)].value));
   342   } else {
   343     aFacingMode.Truncate(0);
   344   }
   345   return NS_OK;
   346 }
   348 MediaEngineVideoSource*
   349 VideoDevice::GetSource()
   350 {
   351   return static_cast<MediaEngineVideoSource*>(&*mSource);
   352 }
   354 MediaEngineAudioSource*
   355 AudioDevice::GetSource()
   356 {
   357   return static_cast<MediaEngineAudioSource*>(&*mSource);
   358 }
   360 /**
   361  * A subclass that we only use to stash internal pointers to MediaStreamGraph objects
   362  * that need to be cleaned up.
   363  */
   364 class nsDOMUserMediaStream : public DOMLocalMediaStream
   365 {
   366 public:
   367   static already_AddRefed<nsDOMUserMediaStream>
   368   CreateTrackUnionStream(nsIDOMWindow* aWindow,
   369                          MediaEngineSource *aAudioSource,
   370                          MediaEngineSource *aVideoSource)
   371   {
   372     DOMMediaStream::TrackTypeHints hints =
   373       (aAudioSource ? DOMMediaStream::HINT_CONTENTS_AUDIO : 0) |
   374       (aVideoSource ? DOMMediaStream::HINT_CONTENTS_VIDEO : 0);
   376     nsRefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aAudioSource);
   377     stream->InitTrackUnionStream(aWindow, hints);
   378     return stream.forget();
   379   }
   381   nsDOMUserMediaStream(MediaEngineSource *aAudioSource) :
   382     mAudioSource(aAudioSource),
   383     mEchoOn(true),
   384     mAgcOn(false),
   385     mNoiseOn(true),
   386 #ifdef MOZ_WEBRTC
   387     mEcho(webrtc::kEcDefault),
   388     mAgc(webrtc::kAgcDefault),
   389     mNoise(webrtc::kNsDefault),
   390 #else
   391     mEcho(0),
   392     mAgc(0),
   393     mNoise(0),
   394 #endif
   395     mPlayoutDelay(20)
   396   {}
   398   virtual ~nsDOMUserMediaStream()
   399   {
   400     Stop();
   402     if (mPort) {
   403       mPort->Destroy();
   404     }
   405     if (mSourceStream) {
   406       mSourceStream->Destroy();
   407     }
   408   }
   410   virtual void Stop()
   411   {
   412     if (mSourceStream) {
   413       mSourceStream->EndAllTrackAndFinish();
   414     }
   415   }
   417   // Allow getUserMedia to pass input data directly to PeerConnection/MediaPipeline
   418   virtual bool AddDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE
   419   {
   420     if (mSourceStream) {
   421       mSourceStream->AddDirectListener(aListener);
   422       return true; // application should ignore NotifyQueuedTrackData
   423     }
   424     return false;
   425   }
   427   virtual void
   428   AudioConfig(bool aEchoOn, uint32_t aEcho,
   429               bool aAgcOn, uint32_t aAgc,
   430               bool aNoiseOn, uint32_t aNoise,
   431               int32_t aPlayoutDelay)
   432   {
   433     mEchoOn = aEchoOn;
   434     mEcho = aEcho;
   435     mAgcOn = aAgcOn;
   436     mAgc = aAgc;
   437     mNoiseOn = aNoiseOn;
   438     mNoise = aNoise;
   439     mPlayoutDelay = aPlayoutDelay;
   440   }
   442   virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE
   443   {
   444     if (mSourceStream) {
   445       mSourceStream->RemoveDirectListener(aListener);
   446     }
   447   }
   449   // let us intervene for direct listeners when someone does track.enabled = false
   450   virtual void SetTrackEnabled(TrackID aID, bool aEnabled) MOZ_OVERRIDE
   451   {
   452     // We encapsulate the SourceMediaStream and TrackUnion into one entity, so
   453     // we can handle the disabling at the SourceMediaStream
   455     // We need to find the input track ID for output ID aID, so we let the TrackUnion
   456     // forward the request to the source and translate the ID
   457     GetStream()->AsProcessedStream()->ForwardTrackEnabled(aID, aEnabled);
   458   }
   460   // The actual MediaStream is a TrackUnionStream. But these resources need to be
   461   // explicitly destroyed too.
   462   nsRefPtr<SourceMediaStream> mSourceStream;
   463   nsRefPtr<MediaInputPort> mPort;
   464   nsRefPtr<MediaEngineSource> mAudioSource; // so we can turn on AEC
   465   bool mEchoOn;
   466   bool mAgcOn;
   467   bool mNoiseOn;
   468   uint32_t mEcho;
   469   uint32_t mAgc;
   470   uint32_t mNoise;
   471   uint32_t mPlayoutDelay;
   472 };
   474 /**
   475  * Creates a MediaStream, attaches a listener and fires off a success callback
   476  * to the DOM with the stream. We also pass in the error callback so it can
   477  * be released correctly.
   478  *
   479  * All of this must be done on the main thread!
   480  *
   481  * Note that the various GetUserMedia Runnable classes currently allow for
   482  * two streams.  If we ever need to support getting more than two streams
   483  * at once, we could convert everything to nsTArray<nsRefPtr<blah> >'s,
   484  * though that would complicate the constructors some.  Currently the
   485  * GetUserMedia spec does not allow for more than 2 streams to be obtained in
   486  * one call, to simplify handling of constraints.
   487  */
   488 class GetUserMediaStreamRunnable : public nsRunnable
   489 {
   490 public:
   491   GetUserMediaStreamRunnable(
   492     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
   493     nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
   494     uint64_t aWindowID,
   495     GetUserMediaCallbackMediaStreamListener* aListener,
   496     MediaEngineSource* aAudioSource,
   497     MediaEngineSource* aVideoSource)
   498     : mAudioSource(aAudioSource)
   499     , mVideoSource(aVideoSource)
   500     , mWindowID(aWindowID)
   501     , mListener(aListener)
   502     , mManager(MediaManager::GetInstance())
   503   {
   504     mSuccess.swap(aSuccess);
   505     mError.swap(aError);
   506   }
   508   ~GetUserMediaStreamRunnable() {}
   510   class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback
   511   {
   512   public:
   513     TracksAvailableCallback(MediaManager* aManager,
   514                             nsIDOMGetUserMediaSuccessCallback* aSuccess,
   515                             uint64_t aWindowID,
   516                             DOMMediaStream* aStream)
   517       : mWindowID(aWindowID), mSuccess(aSuccess), mManager(aManager),
   518         mStream(aStream) {}
   519     virtual void NotifyTracksAvailable(DOMMediaStream* aStream) MOZ_OVERRIDE
   520     {
   521       // We're in the main thread, so no worries here.
   522       if (!(mManager->IsWindowStillActive(mWindowID))) {
   523         return;
   524       }
   526       // Start currentTime from the point where this stream was successfully
   527       // returned.
   528       aStream->SetLogicalStreamStartTime(aStream->GetStream()->GetCurrentTime());
   530       // This is safe since we're on main-thread, and the windowlist can only
   531       // be invalidated from the main-thread (see OnNavigation)
   532       LOG(("Returning success for getUserMedia()"));
   533       mSuccess->OnSuccess(aStream);
   534     }
   535     uint64_t mWindowID;
   536     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
   537     nsRefPtr<MediaManager> mManager;
   538     // Keep the DOMMediaStream alive until the NotifyTracksAvailable callback
   539     // has fired, otherwise we might immediately destroy the DOMMediaStream and
   540     // shut down the underlying MediaStream prematurely.
   541     // This creates a cycle which is broken when NotifyTracksAvailable
   542     // is fired (which will happen unless the browser shuts down,
   543     // since we only add this callback when we've successfully appended
   544     // the desired tracks in the MediaStreamGraph) or when
   545     // DOMMediaStream::NotifyMediaStreamGraphShutdown is called.
   546     nsRefPtr<DOMMediaStream> mStream;
   547   };
   549   NS_IMETHOD
   550   Run()
   551   {
   552 #ifdef MOZ_WEBRTC
   553     int32_t aec = (int32_t) webrtc::kEcUnchanged;
   554     int32_t agc = (int32_t) webrtc::kAgcUnchanged;
   555     int32_t noise = (int32_t) webrtc::kNsUnchanged;
   556 #else
   557     int32_t aec = 0, agc = 0, noise = 0;
   558 #endif
   559     bool aec_on = false, agc_on = false, noise_on = false;
   560     int32_t playout_delay = 0;
   562     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   563     nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
   564       (nsGlobalWindow::GetInnerWindowWithId(mWindowID));
   566     // We're on main-thread, and the windowlist can only
   567     // be invalidated from the main-thread (see OnNavigation)
   568     StreamListeners* listeners = mManager->GetWindowListeners(mWindowID);
   569     if (!listeners || !window || !window->GetExtantDoc()) {
   570       // This window is no longer live.  mListener has already been removed
   571       return NS_OK;
   572     }
   574 #ifdef MOZ_WEBRTC
   575     // Right now these configs are only of use if webrtc is available
   576     nsresult rv;
   577     nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
   578     if (NS_SUCCEEDED(rv)) {
   579       nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
   581       if (branch) {
   582         branch->GetBoolPref("media.getusermedia.aec_enabled", &aec_on);
   583         branch->GetIntPref("media.getusermedia.aec", &aec);
   584         branch->GetBoolPref("media.getusermedia.agc_enabled", &agc_on);
   585         branch->GetIntPref("media.getusermedia.agc", &agc);
   586         branch->GetBoolPref("media.getusermedia.noise_enabled", &noise_on);
   587         branch->GetIntPref("media.getusermedia.noise", &noise);
   588         branch->GetIntPref("media.getusermedia.playout_delay", &playout_delay);
   589       }
   590     }
   591 #endif
   592     // Create a media stream.
   593     nsRefPtr<nsDOMUserMediaStream> trackunion =
   594       nsDOMUserMediaStream::CreateTrackUnionStream(window, mAudioSource,
   595                                                    mVideoSource);
   596     if (!trackunion) {
   597       nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
   598       LOG(("Returning error for getUserMedia() - no stream"));
   599       error->OnError(NS_LITERAL_STRING("NO_STREAM"));
   600       return NS_OK;
   601     }
   602     trackunion->AudioConfig(aec_on, (uint32_t) aec,
   603                             agc_on, (uint32_t) agc,
   604                             noise_on, (uint32_t) noise,
   605                             playout_delay);
   608     MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
   609     nsRefPtr<SourceMediaStream> stream = gm->CreateSourceStream(nullptr);
   611     // connect the source stream to the track union stream to avoid us blocking
   612     trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true);
   613     nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()->
   614       AllocateInputPort(stream, MediaInputPort::FLAG_BLOCK_OUTPUT);
   615     trackunion->mSourceStream = stream;
   616     trackunion->mPort = port.forget();
   617     // Log the relationship between SourceMediaStream and TrackUnion stream
   618     // Make sure logger starts before capture
   619     AsyncLatencyLogger::Get(true);
   620     LogLatency(AsyncLatencyLogger::MediaStreamCreate,
   621                reinterpret_cast<uint64_t>(stream.get()),
   622                reinterpret_cast<int64_t>(trackunion->GetStream()));
   624     trackunion->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal());
   626     // The listener was added at the begining in an inactive state.
   627     // Activate our listener. We'll call Start() on the source when get a callback
   628     // that the MediaStream has started consuming. The listener is freed
   629     // when the page is invalidated (on navigation or close).
   630     mListener->Activate(stream.forget(), mAudioSource, mVideoSource);
   632     // Note: includes JS callbacks; must be released on MainThread
   633     TracksAvailableCallback* tracksAvailableCallback =
   634       new TracksAvailableCallback(mManager, mSuccess, mWindowID, trackunion);
   636     mListener->AudioConfig(aec_on, (uint32_t) aec,
   637                            agc_on, (uint32_t) agc,
   638                            noise_on, (uint32_t) noise,
   639                            playout_delay);
   641     // Dispatch to the media thread to ask it to start the sources,
   642     // because that can take a while.
   643     // Pass ownership of trackunion to the MediaOperationRunnable
   644     // to ensure it's kept alive until the MediaOperationRunnable runs (at least).
   645     nsIThread *mediaThread = MediaManager::GetThread();
   646     nsRefPtr<MediaOperationRunnable> runnable(
   647       new MediaOperationRunnable(MEDIA_START, mListener, trackunion,
   648                                  tracksAvailableCallback,
   649                                  mAudioSource, mVideoSource, false, mWindowID,
   650                                  mError.forget()));
   651     mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
   653     // We won't need mError now.
   654     mError = nullptr;
   655     return NS_OK;
   656   }
   658 private:
   659   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
   660   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
   661   nsRefPtr<MediaEngineSource> mAudioSource;
   662   nsRefPtr<MediaEngineSource> mVideoSource;
   663   uint64_t mWindowID;
   664   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   665   nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
   666 };
   668 static bool
   669 IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) {
   670   return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
   671 }
   673 static const MediaTrackConstraints&
   674 GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) {
   675   static const MediaTrackConstraints empty;
   676   return aUnion.IsMediaTrackConstraints() ?
   677       aUnion.GetAsMediaTrackConstraints() : empty;
   678 }
   680 /**
   681  * Helper functions that implement the constraints algorithm from
   682  * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
   683  */
   685 // Reminder: add handling for new constraints both here and in GetSources below!
   687 static bool SatisfyConstraintSet(const MediaEngineVideoSource *,
   688                                  const MediaTrackConstraintSet &aConstraints,
   689                                  nsIMediaDevice &aCandidate)
   690 {
   691   if (aConstraints.mFacingMode.WasPassed()) {
   692     nsString s;
   693     aCandidate.GetFacingMode(s);
   694     if (!s.EqualsASCII(dom::VideoFacingModeEnumValues::strings[
   695         uint32_t(aConstraints.mFacingMode.Value())].value)) {
   696       return false;
   697     }
   698   }
   699   // TODO: Add more video-specific constraints
   700   return true;
   701 }
   703 static bool SatisfyConstraintSet(const MediaEngineAudioSource *,
   704                                  const MediaTrackConstraintSet &aConstraints,
   705                                  nsIMediaDevice &aCandidate)
   706 {
   707   // TODO: Add audio-specific constraints
   708   return true;
   709 }
   711 typedef nsTArray<nsCOMPtr<nsIMediaDevice> > SourceSet;
   713 // Source getter that constrains list returned
   715 template<class SourceType, class ConstraintsType>
   716 static SourceSet *
   717   GetSources(MediaEngine *engine,
   718              ConstraintsType &aConstraints,
   719              void (MediaEngine::* aEnumerate)(nsTArray<nsRefPtr<SourceType> >*),
   720              char* media_device_name = nullptr)
   721 {
   722   ScopedDeletePtr<SourceSet> result(new SourceSet);
   724   const SourceType * const type = nullptr;
   725   nsString deviceName;
   726   // First collect sources
   727   SourceSet candidateSet;
   728   {
   729     nsTArray<nsRefPtr<SourceType> > sources;
   730     (engine->*aEnumerate)(&sources);
   731     /**
   732       * We're allowing multiple tabs to access the same camera for parity
   733       * with Chrome.  See bug 811757 for some of the issues surrounding
   734       * this decision.  To disallow, we'd filter by IsAvailable() as we used
   735       * to.
   736       */
   737     for (uint32_t len = sources.Length(), i = 0; i < len; i++) {
   738 #ifdef DEBUG
   739       sources[i]->GetName(deviceName);
   740       if (media_device_name && strlen(media_device_name) > 0)  {
   741         if (deviceName.EqualsASCII(media_device_name)) {
   742           candidateSet.AppendElement(MediaDevice::Create(sources[i]));
   743           break;
   744         }
   745       } else {
   746 #endif
   747         candidateSet.AppendElement(MediaDevice::Create(sources[i]));
   748 #ifdef DEBUG
   749       }
   750 #endif
   751     }
   752   }
   754   // Apply constraints to the list of sources.
   756   auto& c = aConstraints;
   757   if (c.mUnsupportedRequirement) {
   758     // Check upfront the names of required constraints that are unsupported for
   759     // this media-type. The spec requires these to fail, so getting them out of
   760     // the way early provides a necessary invariant for the remaining algorithm
   761     // which maximizes code-reuse by ignoring constraints of the other type
   762     // (specifically, SatisfyConstraintSet is reused for the advanced algorithm
   763     // where the spec requires it to ignore constraints of the other type)
   764     return result.forget();
   765   }
   767   // Now on to the actual algorithm: First apply required constraints.
   769   for (uint32_t i = 0; i < candidateSet.Length();) {
   770     // Overloading instead of template specialization keeps things local
   771     if (!SatisfyConstraintSet(type, c.mRequired, *candidateSet[i])) {
   772       candidateSet.RemoveElementAt(i);
   773     } else {
   774       ++i;
   775     }
   776   }
   778   // TODO(jib): Proper non-ordered handling of nonrequired constraints (907352)
   779   //
   780   // For now, put nonrequired constraints at tail of Advanced list.
   781   // This isn't entirely accurate, as order will matter, but few will notice
   782   // the difference until we get camera selection and a few more constraints.
   783   if (c.mNonrequired.Length()) {
   784     if (!c.mAdvanced.WasPassed()) {
   785       c.mAdvanced.Construct();
   786     }
   787     c.mAdvanced.Value().MoveElementsFrom(c.mNonrequired);
   788   }
   790   // Then apply advanced (formerly known as optional) constraints.
   791   //
   792   // These are only effective when there are multiple sources to pick from.
   793   // Spec as-of-this-writing says to run algorithm on "all possible tracks
   794   // of media type T that the browser COULD RETURN" (emphasis added).
   795   //
   796   // We think users ultimately control which devices we could return, so after
   797   // determining the webpage's preferred list, we add the remaining choices
   798   // to the tail, reasoning that they would all have passed individually,
   799   // i.e. if the user had any one of them as their sole device (enabled).
   800   //
   801   // This avoids users having to unplug/disable devices should a webpage pick
   802   // the wrong one (UX-fail). Webpage-preferred devices will be listed first.
   804   SourceSet tailSet;
   806   if (c.mAdvanced.WasPassed()) {
   807     auto &array = c.mAdvanced.Value();
   809     for (int i = 0; i < int(array.Length()); i++) {
   810       SourceSet rejects;
   811       for (uint32_t j = 0; j < candidateSet.Length();) {
   812         if (!SatisfyConstraintSet(type, array[i], *candidateSet[j])) {
   813           rejects.AppendElement(candidateSet[j]);
   814           candidateSet.RemoveElementAt(j);
   815         } else {
   816           ++j;
   817         }
   818       }
   819       (candidateSet.Length()? tailSet : candidateSet).MoveElementsFrom(rejects);
   820     }
   821   }
   823   // TODO: Proper non-ordered handling of nonrequired constraints (Bug 907352)
   825   result->MoveElementsFrom(candidateSet);
   826   result->MoveElementsFrom(tailSet);
   827   return result.forget();
   828 }
   830 /**
   831  * Runs on a seperate thread and is responsible for enumerating devices.
   832  * Depending on whether a picture or stream was asked for, either
   833  * ProcessGetUserMedia or ProcessGetUserMediaSnapshot is called, and the results
   834  * are sent back to the DOM.
   835  *
   836  * Do not run this on the main thread. The success and error callbacks *MUST*
   837  * be dispatched on the main thread!
   838  */
   839 class GetUserMediaRunnable : public nsRunnable
   840 {
   841 public:
   842   GetUserMediaRunnable(
   843     const MediaStreamConstraints& aConstraints,
   844     already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
   845     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
   846     uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
   847     MediaEnginePrefs &aPrefs)
   848     : mConstraints(aConstraints)
   849     , mSuccess(aSuccess)
   850     , mError(aError)
   851     , mWindowID(aWindowID)
   852     , mListener(aListener)
   853     , mPrefs(aPrefs)
   854     , mDeviceChosen(false)
   855     , mBackend(nullptr)
   856     , mManager(MediaManager::GetInstance())
   857   {}
   859   /**
   860    * The caller can also choose to provide their own backend instead of
   861    * using the one provided by MediaManager::GetBackend.
   862    */
   863   GetUserMediaRunnable(
   864     const MediaStreamConstraints& aConstraints,
   865     already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
   866     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
   867     uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
   868     MediaEnginePrefs &aPrefs,
   869     MediaEngine* aBackend)
   870     : mConstraints(aConstraints)
   871     , mSuccess(aSuccess)
   872     , mError(aError)
   873     , mWindowID(aWindowID)
   874     , mListener(aListener)
   875     , mPrefs(aPrefs)
   876     , mDeviceChosen(false)
   877     , mBackend(aBackend)
   878     , mManager(MediaManager::GetInstance())
   879   {}
   881   ~GetUserMediaRunnable() {
   882   }
   884   void
   885   Fail(const nsAString& aMessage) {
   886     nsRefPtr<ErrorCallbackRunnable> runnable =
   887       new ErrorCallbackRunnable(mSuccess, mError, aMessage, mWindowID);
   888     // These should be empty now
   889     MOZ_ASSERT(!mSuccess);
   890     MOZ_ASSERT(!mError);
   892     NS_DispatchToMainThread(runnable);
   893   }
   895   NS_IMETHOD
   896   Run()
   897   {
   898     NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   899     MOZ_ASSERT(mSuccess);
   900     MOZ_ASSERT(mError);
   902     MediaEngine* backend = mBackend;
   903     // Was a backend provided?
   904     if (!backend) {
   905       backend = mManager->GetBackend(mWindowID);
   906     }
   908     // Was a device provided?
   909     if (!mDeviceChosen) {
   910       nsresult rv = SelectDevice(backend);
   911       if (rv != NS_OK) {
   912         return rv;
   913       }
   914     }
   916     // It is an error if audio or video are requested along with picture.
   917     if (mConstraints.mPicture &&
   918         (IsOn(mConstraints.mAudio) || IsOn(mConstraints.mVideo))) {
   919       Fail(NS_LITERAL_STRING("NOT_SUPPORTED_ERR"));
   920       return NS_OK;
   921     }
   923     if (mConstraints.mPicture) {
   924       ProcessGetUserMediaSnapshot(mVideoDevice->GetSource(), 0);
   925       return NS_OK;
   926     }
   928     // There's a bug in the permission code that can leave us with mAudio but no audio device
   929     ProcessGetUserMedia(((IsOn(mConstraints.mAudio) && mAudioDevice) ?
   930                          mAudioDevice->GetSource() : nullptr),
   931                         ((IsOn(mConstraints.mVideo) && mVideoDevice) ?
   932                          mVideoDevice->GetSource() : nullptr));
   933     return NS_OK;
   934   }
   936   nsresult
   937   Denied(const nsAString& aErrorMsg)
   938   {
   939     MOZ_ASSERT(mSuccess);
   940     MOZ_ASSERT(mError);
   942     // We add a disabled listener to the StreamListeners array until accepted
   943     // If this was the only active MediaStream, remove the window from the list.
   944     if (NS_IsMainThread()) {
   945       // This is safe since we're on main-thread, and the window can only
   946       // be invalidated from the main-thread (see OnNavigation)
   947       nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget();
   948       nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
   949       error->OnError(aErrorMsg);
   951       // Should happen *after* error runs for consistency, but may not matter
   952       nsRefPtr<MediaManager> manager(MediaManager::GetInstance());
   953       manager->RemoveFromWindowList(mWindowID, mListener);
   954     } else {
   955       // This will re-check the window being alive on main-thread
   956       // Note: we must remove the listener on MainThread as well
   957       Fail(aErrorMsg);
   959       // MUST happen after ErrorCallbackRunnable Run()s, as it checks the active window list
   960       NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, mListener));
   961     }
   963     MOZ_ASSERT(!mSuccess);
   964     MOZ_ASSERT(!mError);
   966     return NS_OK;
   967   }
   969   nsresult
   970   SetContraints(const MediaStreamConstraints& aConstraints)
   971   {
   972     mConstraints = aConstraints;
   973     return NS_OK;
   974   }
   976   nsresult
   977   SetAudioDevice(AudioDevice* aAudioDevice)
   978   {
   979     mAudioDevice = aAudioDevice;
   980     mDeviceChosen = true;
   981     return NS_OK;
   982   }
   984   nsresult
   985   SetVideoDevice(VideoDevice* aVideoDevice)
   986   {
   987     mVideoDevice = aVideoDevice;
   988     mDeviceChosen = true;
   989     return NS_OK;
   990   }
   992   nsresult
   993   SelectDevice(MediaEngine* backend)
   994   {
   995     MOZ_ASSERT(mSuccess);
   996     MOZ_ASSERT(mError);
   997     if (mConstraints.mPicture || IsOn(mConstraints.mVideo)) {
   998       VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
   999       ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints,
  1000           &MediaEngine::EnumerateVideoDevices));
  1002       if (!sources->Length()) {
  1003         Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
  1004         return NS_ERROR_FAILURE;
  1006       // Pick the first available device.
  1007       mVideoDevice = do_QueryObject((*sources)[0]);
  1008       LOG(("Selected video device"));
  1011     if (IsOn(mConstraints.mAudio)) {
  1012       AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio));
  1013       ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints,
  1014           &MediaEngine::EnumerateAudioDevices));
  1016       if (!sources->Length()) {
  1017         Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
  1018         return NS_ERROR_FAILURE;
  1020       // Pick the first available device.
  1021       mAudioDevice = do_QueryObject((*sources)[0]);
  1022       LOG(("Selected audio device"));
  1025     return NS_OK;
  1028   /**
  1029    * Allocates a video or audio device and returns a MediaStream via
  1030    * a GetUserMediaStreamRunnable. Runs off the main thread.
  1031    */
  1032   void
  1033   ProcessGetUserMedia(MediaEngineAudioSource* aAudioSource,
  1034                       MediaEngineVideoSource* aVideoSource)
  1036     MOZ_ASSERT(mSuccess);
  1037     MOZ_ASSERT(mError);
  1038     nsresult rv;
  1039     if (aAudioSource) {
  1040       rv = aAudioSource->Allocate(GetInvariant(mConstraints.mAudio), mPrefs);
  1041       if (NS_FAILED(rv)) {
  1042         LOG(("Failed to allocate audiosource %d",rv));
  1043         Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
  1044         return;
  1047     if (aVideoSource) {
  1048       rv = aVideoSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs);
  1049       if (NS_FAILED(rv)) {
  1050         LOG(("Failed to allocate videosource %d\n",rv));
  1051         if (aAudioSource) {
  1052           aAudioSource->Deallocate();
  1054         Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
  1055         return;
  1059     NS_DispatchToMainThread(new GetUserMediaStreamRunnable(
  1060       mSuccess, mError, mWindowID, mListener, aAudioSource, aVideoSource
  1061     ));
  1063     MOZ_ASSERT(!mSuccess);
  1064     MOZ_ASSERT(!mError);
  1066     return;
  1069   /**
  1070    * Allocates a video device, takes a snapshot and returns a DOMFile via
  1071    * a SuccessRunnable or an error via the ErrorRunnable. Off the main thread.
  1072    */
  1073   void
  1074   ProcessGetUserMediaSnapshot(MediaEngineVideoSource* aSource, int aDuration)
  1076     MOZ_ASSERT(mSuccess);
  1077     MOZ_ASSERT(mError);
  1078     nsresult rv = aSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs);
  1079     if (NS_FAILED(rv)) {
  1080       Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
  1081       return;
  1084     /**
  1085      * Display picture capture UI here before calling Snapshot() - Bug 748835.
  1086      */
  1087     nsCOMPtr<nsIDOMFile> file;
  1088     aSource->Snapshot(aDuration, getter_AddRefs(file));
  1089     aSource->Deallocate();
  1091     NS_DispatchToMainThread(new SuccessCallbackRunnable(
  1092       mSuccess, mError, file, mWindowID
  1093     ));
  1095     MOZ_ASSERT(!mSuccess);
  1096     MOZ_ASSERT(!mError);
  1098     return;
  1101 private:
  1102   MediaStreamConstraints mConstraints;
  1104   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
  1105   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
  1106   uint64_t mWindowID;
  1107   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
  1108   nsRefPtr<AudioDevice> mAudioDevice;
  1109   nsRefPtr<VideoDevice> mVideoDevice;
  1110   MediaEnginePrefs mPrefs;
  1112   bool mDeviceChosen;
  1114   RefPtr<MediaEngine> mBackend;
  1115   nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
  1116 };
  1118 /**
  1119  * Similar to GetUserMediaRunnable, but used for the chrome-only
  1120  * GetUserMediaDevices function. Enumerates a list of audio & video devices,
  1121  * wraps them up in nsIMediaDevice objects and returns it to the success
  1122  * callback.
  1123  */
  1124 class GetUserMediaDevicesRunnable : public nsRunnable
  1126 public:
  1127   GetUserMediaDevicesRunnable(
  1128     const MediaStreamConstraints& aConstraints,
  1129     already_AddRefed<nsIGetUserMediaDevicesSuccessCallback> aSuccess,
  1130     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
  1131     uint64_t aWindowId, char* aAudioLoopbackDev, char* aVideoLoopbackDev)
  1132     : mConstraints(aConstraints)
  1133     , mSuccess(aSuccess)
  1134     , mError(aError)
  1135     , mManager(MediaManager::GetInstance())
  1136     , mWindowId(aWindowId)
  1137     , mLoopbackAudioDevice(aAudioLoopbackDev)
  1138     , mLoopbackVideoDevice(aVideoLoopbackDev) {}
  1140   NS_IMETHOD
  1141   Run()
  1143     NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1145     nsRefPtr<MediaEngine> backend;
  1146     if (mConstraints.mFake)
  1147       backend = new MediaEngineDefault();
  1148     else
  1149       backend = mManager->GetBackend(mWindowId);
  1151     ScopedDeletePtr<SourceSet> final(new SourceSet);
  1152     if (IsOn(mConstraints.mVideo)) {
  1153       VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
  1154       ScopedDeletePtr<SourceSet> s(GetSources(backend, constraints,
  1155           &MediaEngine::EnumerateVideoDevices,
  1156           mLoopbackVideoDevice));
  1157       final->MoveElementsFrom(*s);
  1159     if (IsOn(mConstraints.mAudio)) {
  1160       AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio));
  1161       ScopedDeletePtr<SourceSet> s (GetSources(backend, constraints,
  1162           &MediaEngine::EnumerateAudioDevices,
  1163           mLoopbackAudioDevice));
  1164       final->MoveElementsFrom(*s);
  1166     NS_DispatchToMainThread(new DeviceSuccessCallbackRunnable(mWindowId,
  1167                                                               mSuccess, mError,
  1168                                                               final.forget()));
  1169     // DeviceSuccessCallbackRunnable should have taken these.
  1170     MOZ_ASSERT(!mSuccess && !mError);
  1171     return NS_OK;
  1174 private:
  1175   MediaStreamConstraints mConstraints;
  1176   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mSuccess;
  1177   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
  1178   nsRefPtr<MediaManager> mManager;
  1179   uint64_t mWindowId;
  1180   const nsString mCallId;
  1181   // Audio & Video loopback devices to be used based on
  1182   // the preference settings. This is currently used for
  1183   // automated media tests only.
  1184   char* mLoopbackAudioDevice;
  1185   char* mLoopbackVideoDevice;
  1186 };
  1188 MediaManager::MediaManager()
  1189   : mMediaThread(nullptr)
  1190   , mMutex("mozilla::MediaManager")
  1191   , mBackend(nullptr) {
  1192   mPrefs.mWidth  = 0; // adaptive default
  1193   mPrefs.mHeight = 0; // adaptive default
  1194   mPrefs.mFPS    = MediaEngine::DEFAULT_VIDEO_FPS;
  1195   mPrefs.mMinFPS = MediaEngine::DEFAULT_VIDEO_MIN_FPS;
  1197   nsresult rv;
  1198   nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
  1199   if (NS_SUCCEEDED(rv)) {
  1200     nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
  1201     if (branch) {
  1202       GetPrefs(branch, nullptr);
  1205   LOG(("%s: default prefs: %dx%d @%dfps (min %d)", __FUNCTION__,
  1206        mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS));
  1209 NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver)
  1211 /* static */ StaticRefPtr<MediaManager> MediaManager::sSingleton;
  1213 // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
  1214 // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
  1215 // from MediaManager thread.
  1216 /* static */  MediaManager*
  1217 MediaManager::Get() {
  1218   if (!sSingleton) {
  1219     sSingleton = new MediaManager();
  1221     NS_NewNamedThread("MediaManager", getter_AddRefs(sSingleton->mMediaThread));
  1222     LOG(("New Media thread for gum"));
  1224     NS_ASSERTION(NS_IsMainThread(), "Only create MediaManager on main thread");
  1225     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  1226     if (obs) {
  1227       obs->AddObserver(sSingleton, "xpcom-shutdown", false);
  1228       obs->AddObserver(sSingleton, "getUserMedia:response:allow", false);
  1229       obs->AddObserver(sSingleton, "getUserMedia:response:deny", false);
  1230       obs->AddObserver(sSingleton, "getUserMedia:revoke", false);
  1231       obs->AddObserver(sSingleton, "phone-state-changed", false);
  1233     // else MediaManager won't work properly and will leak (see bug 837874)
  1234     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
  1235     if (prefs) {
  1236       prefs->AddObserver("media.navigator.video.default_width", sSingleton, false);
  1237       prefs->AddObserver("media.navigator.video.default_height", sSingleton, false);
  1238       prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false);
  1239       prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false);
  1242   return sSingleton;
  1245 /* static */ already_AddRefed<MediaManager>
  1246 MediaManager::GetInstance()
  1248   // so we can have non-refcounted getters
  1249   nsRefPtr<MediaManager> service = MediaManager::Get();
  1250   return service.forget();
  1253 /* static */ nsresult
  1254 MediaManager::NotifyRecordingStatusChange(nsPIDOMWindow* aWindow,
  1255                                           const nsString& aMsg,
  1256                                           const bool& aIsAudio,
  1257                                           const bool& aIsVideo)
  1259   NS_ENSURE_ARG(aWindow);
  1261   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  1262   if (!obs) {
  1263     NS_WARNING("Could not get the Observer service for GetUserMedia recording notification.");
  1264     return NS_ERROR_FAILURE;
  1267   nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
  1268   props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
  1269   props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
  1271   bool isApp = false;
  1272   nsString requestURL;
  1274   if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) {
  1275     nsresult rv = docShell->GetIsApp(&isApp);
  1276     NS_ENSURE_SUCCESS(rv, rv);
  1278     if (isApp) {
  1279       rv = docShell->GetAppManifestURL(requestURL);
  1280       NS_ENSURE_SUCCESS(rv, rv);
  1284   if (!isApp) {
  1285     nsCString pageURL;
  1286     nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
  1287     NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE);
  1289     nsresult rv = docURI->GetSpec(pageURL);
  1290     NS_ENSURE_SUCCESS(rv, rv);
  1292     requestURL = NS_ConvertUTF8toUTF16(pageURL);
  1295   props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
  1296   props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
  1298   obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
  1299 		       "recording-device-events",
  1300 		       aMsg.get());
  1302   // Forward recording events to parent process.
  1303   // The events are gathered in chrome process and used for recording indicator
  1304   if (XRE_GetProcessType() != GeckoProcessType_Default) {
  1305     unused <<
  1306       dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(aMsg,
  1307                                                                    requestURL,
  1308                                                                    aIsAudio,
  1309                                                                    aIsVideo);
  1312   return NS_OK;
  1315 /**
  1316  * The entry point for this file. A call from Navigator::mozGetUserMedia
  1317  * will end up here. MediaManager is a singleton that is responsible
  1318  * for handling all incoming getUserMedia calls from every window.
  1319  */
  1320 nsresult
  1321 MediaManager::GetUserMedia(bool aPrivileged,
  1322   nsPIDOMWindow* aWindow, const MediaStreamConstraints& aConstraints,
  1323   nsIDOMGetUserMediaSuccessCallback* aOnSuccess,
  1324   nsIDOMGetUserMediaErrorCallback* aOnError)
  1326   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1328   NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER);
  1329   NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER);
  1330   NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);
  1332   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess);
  1333   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError);
  1335   MediaStreamConstraints c(aConstraints); // copy
  1337   /**
  1338    * If we were asked to get a picture, before getting a snapshot, we check if
  1339    * the calling page is allowed to open a popup. We do this because
  1340    * {picture:true} will open a new "window" to let the user preview or select
  1341    * an image, on Android. The desktop UI for {picture:true} is TBD, at which
  1342    * may point we can decide whether to extend this test there as well.
  1343    */
  1344 #if !defined(MOZ_WEBRTC)
  1345   if (c.mPicture && !aPrivileged) {
  1346     if (aWindow->GetPopupControlState() > openControlled) {
  1347       nsCOMPtr<nsIPopupWindowManager> pm =
  1348         do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID);
  1349       if (!pm) {
  1350         return NS_OK;
  1352       uint32_t permission;
  1353       nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
  1354       pm->TestPermission(doc->NodePrincipal(), &permission);
  1355       if (permission == nsIPopupWindowManager::DENY_POPUP) {
  1356         nsGlobalWindow::FirePopupBlockedEvent(
  1357           doc, aWindow, nullptr, EmptyString(), EmptyString()
  1358         );
  1359         return NS_OK;
  1363 #endif
  1365   static bool created = false;
  1366   if (!created) {
  1367     // Force MediaManager to startup before we try to access it from other threads
  1368     // Hack: should init singleton earlier unless it's expensive (mem or CPU)
  1369     (void) MediaManager::Get();
  1370 #ifdef MOZ_B2G
  1371     // Initialize MediaPermissionManager before send out any permission request.
  1372     (void) MediaPermissionManager::GetInstance();
  1373 #endif //MOZ_B2G
  1376   // Store the WindowID in a hash table and mark as active. The entry is removed
  1377   // when this window is closed or navigated away from.
  1378   uint64_t windowID = aWindow->WindowID();
  1379   // This is safe since we're on main-thread, and the windowlist can only
  1380   // be invalidated from the main-thread (see OnNavigation)
  1381   StreamListeners* listeners = GetActiveWindows()->Get(windowID);
  1382   if (!listeners) {
  1383     listeners = new StreamListeners;
  1384     GetActiveWindows()->Put(windowID, listeners);
  1387   // Ensure there's a thread for gum to proxy to off main thread
  1388   nsIThread *mediaThread = MediaManager::GetThread();
  1390   // Create a disabled listener to act as a placeholder
  1391   GetUserMediaCallbackMediaStreamListener* listener =
  1392     new GetUserMediaCallbackMediaStreamListener(mediaThread, windowID);
  1394   // No need for locking because we always do this in the main thread.
  1395   listeners->AppendElement(listener);
  1397   // Developer preference for turning off permission check.
  1398   if (Preferences::GetBool("media.navigator.permission.disabled", false)) {
  1399     aPrivileged = true;
  1401   if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
  1402     c.mVideo.SetAsBoolean() = false;
  1405 #if defined(ANDROID) || defined(MOZ_WIDGET_GONK)
  1406   // Be backwards compatible only on mobile and only for facingMode.
  1407   if (c.mVideo.IsMediaTrackConstraints()) {
  1408     auto& tc = c.mVideo.GetAsMediaTrackConstraints();
  1409     if (!tc.mRequire.WasPassed() &&
  1410         tc.mMandatory.mFacingMode.WasPassed() && !tc.mFacingMode.WasPassed()) {
  1411       tc.mFacingMode.Construct(tc.mMandatory.mFacingMode.Value());
  1412       tc.mRequire.Construct().AppendElement(NS_LITERAL_STRING("facingMode"));
  1414     if (tc.mOptional.WasPassed() && !tc.mAdvanced.WasPassed()) {
  1415       tc.mAdvanced.Construct();
  1416       for (uint32_t i = 0; i < tc.mOptional.Value().Length(); i++) {
  1417         if (tc.mOptional.Value()[i].mFacingMode.WasPassed()) {
  1418           MediaTrackConstraintSet n;
  1419           n.mFacingMode.Construct(tc.mOptional.Value()[i].mFacingMode.Value());
  1420           tc.mAdvanced.Value().AppendElement(n);
  1425 #endif
  1427   // Pass callbacks and MediaStreamListener along to GetUserMediaRunnable.
  1428   nsRefPtr<GetUserMediaRunnable> runnable;
  1429   if (c.mFake) {
  1430     // Fake stream from default backend.
  1431     runnable = new GetUserMediaRunnable(c, onSuccess.forget(),
  1432       onError.forget(), windowID, listener, mPrefs, new MediaEngineDefault());
  1433   } else {
  1434     // Stream from default device from WebRTC backend.
  1435     runnable = new GetUserMediaRunnable(c, onSuccess.forget(),
  1436       onError.forget(), windowID, listener, mPrefs);
  1439 #ifdef MOZ_B2G_CAMERA
  1440   if (mCameraManager == nullptr) {
  1441     mCameraManager = nsDOMCameraManager::CreateInstance(aWindow);
  1443 #endif
  1445 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
  1446   if (c.mPicture) {
  1447     // ShowFilePickerForMimeType() must run on the Main Thread! (on Android)
  1448     NS_DispatchToMainThread(runnable);
  1449     return NS_OK;
  1451 #endif
  1452   // XXX No full support for picture in Desktop yet (needs proper UI)
  1453   if (aPrivileged ||
  1454       (c.mFake && !Preferences::GetBool("media.navigator.permission.fake"))) {
  1455     mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
  1456   } else {
  1457     bool isHTTPS = false;
  1458     nsIURI* docURI = aWindow->GetDocumentURI();
  1459     if (docURI) {
  1460       docURI->SchemeIs("https", &isHTTPS);
  1463     // Check if this site has persistent permissions.
  1464     nsresult rv;
  1465     nsCOMPtr<nsIPermissionManager> permManager =
  1466       do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
  1467     NS_ENSURE_SUCCESS(rv, rv);
  1469     uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
  1470     if (IsOn(c.mAudio)) {
  1471       rv = permManager->TestExactPermissionFromPrincipal(
  1472         aWindow->GetExtantDoc()->NodePrincipal(), "microphone", &audioPerm);
  1473       NS_ENSURE_SUCCESS(rv, rv);
  1476     uint32_t videoPerm = nsIPermissionManager::UNKNOWN_ACTION;
  1477     if (IsOn(c.mVideo)) {
  1478       rv = permManager->TestExactPermissionFromPrincipal(
  1479         aWindow->GetExtantDoc()->NodePrincipal(), "camera", &videoPerm);
  1480       NS_ENSURE_SUCCESS(rv, rv);
  1483     if ((!IsOn(c.mAudio) || audioPerm == nsIPermissionManager::DENY_ACTION) &&
  1484         (!IsOn(c.mVideo) || videoPerm == nsIPermissionManager::DENY_ACTION)) {
  1485       return runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
  1488     // Ask for user permission, and dispatch runnable (or not) when a response
  1489     // is received via an observer notification. Each call is paired with its
  1490     // runnable by a GUID.
  1491     nsCOMPtr<nsIUUIDGenerator> uuidgen =
  1492       do_GetService("@mozilla.org/uuid-generator;1", &rv);
  1493     NS_ENSURE_SUCCESS(rv, rv);
  1495     // Generate a call ID.
  1496     nsID id;
  1497     rv = uuidgen->GenerateUUIDInPlace(&id);
  1498     NS_ENSURE_SUCCESS(rv, rv);
  1500     char buffer[NSID_LENGTH];
  1501     id.ToProvidedString(buffer);
  1502     NS_ConvertUTF8toUTF16 callID(buffer);
  1504     // Store the current unarmed runnable w/callbacks.
  1505     mActiveCallbacks.Put(callID, runnable);
  1507     // Add a WindowID cross-reference so OnNavigation can tear things down
  1508     nsTArray<nsString>* array;
  1509     if (!mCallIds.Get(windowID, &array)) {
  1510       array = new nsTArray<nsString>();
  1511       array->AppendElement(callID);
  1512       mCallIds.Put(windowID, array);
  1513     } else {
  1514       array->AppendElement(callID);
  1516     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  1517     nsRefPtr<GetUserMediaRequest> req = new GetUserMediaRequest(aWindow,
  1518                                                                 callID, c, isHTTPS);
  1519     obs->NotifyObservers(req, "getUserMedia:request", nullptr);
  1522   return NS_OK;
  1525 nsresult
  1526 MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow,
  1527   const MediaStreamConstraints& aConstraints,
  1528   nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
  1529   nsIDOMGetUserMediaErrorCallback* aOnError,
  1530   uint64_t aInnerWindowID)
  1532   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1534   NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER);
  1535   NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);
  1537   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
  1538   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError);
  1539   char* loopbackAudioDevice = nullptr;
  1540   char* loopbackVideoDevice = nullptr;
  1542 #ifdef DEBUG
  1543   nsresult rv;
  1545   // Check if the preference for using loopback devices is enabled.
  1546   nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
  1547   if (NS_SUCCEEDED(rv)) {
  1548     nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
  1549     if (branch) {
  1550       branch->GetCharPref("media.audio_loopback_dev", &loopbackAudioDevice);
  1551       branch->GetCharPref("media.video_loopback_dev", &loopbackVideoDevice);
  1554 #endif
  1556   nsCOMPtr<nsIRunnable> gUMDRunnable = new GetUserMediaDevicesRunnable(
  1557     aConstraints, onSuccess.forget(), onError.forget(),
  1558     (aInnerWindowID ? aInnerWindowID : aWindow->WindowID()),
  1559     loopbackAudioDevice, loopbackVideoDevice);
  1561   mMediaThread->Dispatch(gUMDRunnable, NS_DISPATCH_NORMAL);
  1562   return NS_OK;
  1565 MediaEngine*
  1566 MediaManager::GetBackend(uint64_t aWindowId)
  1568   // Plugin backends as appropriate. The default engine also currently
  1569   // includes picture support for Android.
  1570   // This IS called off main-thread.
  1571   MutexAutoLock lock(mMutex);
  1572   if (!mBackend) {
  1573 #if defined(MOZ_WEBRTC)
  1574     mBackend = new MediaEngineWebRTC(mPrefs);
  1575 #else
  1576     mBackend = new MediaEngineDefault();
  1577 #endif
  1579   return mBackend;
  1582 static void
  1583 StopSharingCallback(MediaManager *aThis,
  1584                     uint64_t aWindowID,
  1585                     StreamListeners *aListeners,
  1586                     void *aData)
  1588   if (aListeners) {
  1589     auto length = aListeners->Length();
  1590     for (size_t i = 0; i < length; ++i) {
  1591       GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
  1593       if (listener->Stream()) { // aka HasBeenActivate()ed
  1594         listener->Invalidate();
  1596       listener->Remove();
  1598     aListeners->Clear();
  1599     aThis->RemoveWindowID(aWindowID);
  1604 void
  1605 MediaManager::OnNavigation(uint64_t aWindowID)
  1607   NS_ASSERTION(NS_IsMainThread(), "OnNavigation called off main thread");
  1608   LOG(("OnNavigation for %llu", aWindowID));
  1610   // Invalidate this window. The runnables check this value before making
  1611   // a call to content.
  1613   nsTArray<nsString>* callIds;
  1614   if (mCallIds.Get(aWindowID, &callIds)) {
  1615     for (int i = 0, len = callIds->Length(); i < len; ++i) {
  1616       mActiveCallbacks.Remove((*callIds)[i]);
  1618     mCallIds.Remove(aWindowID);
  1621   // This is safe since we're on main-thread, and the windowlist can only
  1622   // be added to from the main-thread
  1623   nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
  1624       (nsGlobalWindow::GetInnerWindowWithId(aWindowID));
  1625   if (window) {
  1626     IterateWindowListeners(window, StopSharingCallback, nullptr);
  1627   } else {
  1628     RemoveWindowID(aWindowID);
  1632 void
  1633 MediaManager::RemoveFromWindowList(uint64_t aWindowID,
  1634   GetUserMediaCallbackMediaStreamListener *aListener)
  1636   NS_ASSERTION(NS_IsMainThread(), "RemoveFromWindowList called off main thread");
  1638   // This is defined as safe on an inactive GUMCMSListener
  1639   aListener->Remove(); // really queues the remove
  1641   StreamListeners* listeners = GetWindowListeners(aWindowID);
  1642   if (!listeners) {
  1643     return;
  1645   listeners->RemoveElement(aListener);
  1646   if (listeners->Length() == 0) {
  1647     RemoveWindowID(aWindowID);
  1648     // listeners has been deleted here
  1650     // get outer windowID
  1651     nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
  1652       (nsGlobalWindow::GetInnerWindowWithId(aWindowID));
  1653     if (window) {
  1654       nsPIDOMWindow *outer = window->GetOuterWindow();
  1655       if (outer) {
  1656         uint64_t outerID = outer->WindowID();
  1658         // Notify the UI that this window no longer has gUM active
  1659         char windowBuffer[32];
  1660         PR_snprintf(windowBuffer, sizeof(windowBuffer), "%llu", outerID);
  1661         nsAutoString data;
  1662         data.Append(NS_ConvertUTF8toUTF16(windowBuffer));
  1664         nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  1665         obs->NotifyObservers(nullptr, "recording-window-ended", data.get());
  1666         LOG(("Sent recording-window-ended for window %llu (outer %llu)",
  1667              aWindowID, outerID));
  1668       } else {
  1669         LOG(("No outer window for inner %llu", aWindowID));
  1671     } else {
  1672       LOG(("No inner window for %llu", aWindowID));
  1677 void
  1678 MediaManager::GetPref(nsIPrefBranch *aBranch, const char *aPref,
  1679                       const char *aData, int32_t *aVal)
  1681   int32_t temp;
  1682   if (aData == nullptr || strcmp(aPref,aData) == 0) {
  1683     if (NS_SUCCEEDED(aBranch->GetIntPref(aPref, &temp))) {
  1684       *aVal = temp;
  1689 void
  1690 MediaManager::GetPrefBool(nsIPrefBranch *aBranch, const char *aPref,
  1691                           const char *aData, bool *aVal)
  1693   bool temp;
  1694   if (aData == nullptr || strcmp(aPref,aData) == 0) {
  1695     if (NS_SUCCEEDED(aBranch->GetBoolPref(aPref, &temp))) {
  1696       *aVal = temp;
  1701 void
  1702 MediaManager::GetPrefs(nsIPrefBranch *aBranch, const char *aData)
  1704   GetPref(aBranch, "media.navigator.video.default_width", aData, &mPrefs.mWidth);
  1705   GetPref(aBranch, "media.navigator.video.default_height", aData, &mPrefs.mHeight);
  1706   GetPref(aBranch, "media.navigator.video.default_fps", aData, &mPrefs.mFPS);
  1707   GetPref(aBranch, "media.navigator.video.default_minfps", aData, &mPrefs.mMinFPS);
  1710 nsresult
  1711 MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
  1712   const char16_t* aData)
  1714   NS_ASSERTION(NS_IsMainThread(), "Observer invoked off the main thread");
  1715   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  1717   if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
  1718     nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) );
  1719     if (branch) {
  1720       GetPrefs(branch,NS_ConvertUTF16toUTF8(aData).get());
  1721       LOG(("%s: %dx%d @%dfps (min %d)", __FUNCTION__,
  1722            mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS));
  1724   } else if (!strcmp(aTopic, "xpcom-shutdown")) {
  1725     obs->RemoveObserver(this, "xpcom-shutdown");
  1726     obs->RemoveObserver(this, "getUserMedia:response:allow");
  1727     obs->RemoveObserver(this, "getUserMedia:response:deny");
  1728     obs->RemoveObserver(this, "getUserMedia:revoke");
  1730     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
  1731     if (prefs) {
  1732       prefs->RemoveObserver("media.navigator.video.default_width", this);
  1733       prefs->RemoveObserver("media.navigator.video.default_height", this);
  1734       prefs->RemoveObserver("media.navigator.video.default_fps", this);
  1735       prefs->RemoveObserver("media.navigator.video.default_minfps", this);
  1738     // Close off any remaining active windows.
  1740       MutexAutoLock lock(mMutex);
  1741       GetActiveWindows()->Clear();
  1742       mActiveCallbacks.Clear();
  1743       mCallIds.Clear();
  1744       LOG(("Releasing MediaManager singleton and thread"));
  1745       // Note: won't be released immediately as the Observer has a ref to us
  1746       sSingleton = nullptr;
  1747       mBackend = nullptr;
  1750     return NS_OK;
  1752   } else if (!strcmp(aTopic, "getUserMedia:response:allow")) {
  1753     nsString key(aData);
  1754     nsRefPtr<GetUserMediaRunnable> runnable;
  1755     if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
  1756       return NS_OK;
  1758     mActiveCallbacks.Remove(key);
  1760     if (aSubject) {
  1761       // A particular device or devices were chosen by the user.
  1762       // NOTE: does not allow setting a device to null; assumes nullptr
  1763       nsCOMPtr<nsISupportsArray> array(do_QueryInterface(aSubject));
  1764       MOZ_ASSERT(array);
  1765       uint32_t len = 0;
  1766       array->Count(&len);
  1767       MOZ_ASSERT(len);
  1768       if (!len) {
  1769         // neither audio nor video were selected
  1770         runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
  1771         return NS_OK;
  1773       for (uint32_t i = 0; i < len; i++) {
  1774         nsCOMPtr<nsISupports> supports;
  1775         array->GetElementAt(i,getter_AddRefs(supports));
  1776         nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supports));
  1777         MOZ_ASSERT(device); // shouldn't be returning anything else...
  1778         if (device) {
  1779           nsString type;
  1780           device->GetType(type);
  1781           if (type.EqualsLiteral("video")) {
  1782             runnable->SetVideoDevice(static_cast<VideoDevice*>(device.get()));
  1783           } else if (type.EqualsLiteral("audio")) {
  1784             runnable->SetAudioDevice(static_cast<AudioDevice*>(device.get()));
  1785           } else {
  1786             NS_WARNING("Unknown device type in getUserMedia");
  1792     // Reuse the same thread to save memory.
  1793     mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
  1794     return NS_OK;
  1796   } else if (!strcmp(aTopic, "getUserMedia:response:deny")) {
  1797     nsString errorMessage(NS_LITERAL_STRING("PERMISSION_DENIED"));
  1799     if (aSubject) {
  1800       nsCOMPtr<nsISupportsString> msg(do_QueryInterface(aSubject));
  1801       MOZ_ASSERT(msg);
  1802       msg->GetData(errorMessage);
  1803       if (errorMessage.IsEmpty())
  1804         errorMessage.Assign(NS_LITERAL_STRING("UNKNOWN_ERROR"));
  1807     nsString key(aData);
  1808     nsRefPtr<GetUserMediaRunnable> runnable;
  1809     if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
  1810       return NS_OK;
  1812     mActiveCallbacks.Remove(key);
  1813     runnable->Denied(errorMessage);
  1814     return NS_OK;
  1816   } else if (!strcmp(aTopic, "getUserMedia:revoke")) {
  1817     nsresult rv;
  1818     uint64_t windowID = nsString(aData).ToInteger64(&rv);
  1819     MOZ_ASSERT(NS_SUCCEEDED(rv));
  1820     if (NS_SUCCEEDED(rv)) {
  1821       LOG(("Revoking MediaCapture access for window %llu",windowID));
  1822       OnNavigation(windowID);
  1825     return NS_OK;
  1827 #ifdef MOZ_WIDGET_GONK
  1828   else if (!strcmp(aTopic, "phone-state-changed")) {
  1829     nsString state(aData);
  1830     if (atoi((const char*)state.get()) == nsIAudioManager::PHONE_STATE_IN_CALL) {
  1831       StopMediaStreams();
  1833     return NS_OK;
  1835 #endif
  1837   return NS_OK;
  1840 static PLDHashOperator
  1841 WindowsHashToArrayFunc (const uint64_t& aId,
  1842                         StreamListeners* aData,
  1843                         void *userArg)
  1845   nsISupportsArray *array =
  1846     static_cast<nsISupportsArray *>(userArg);
  1847   nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
  1848     (nsGlobalWindow::GetInnerWindowWithId(aId));
  1850   MOZ_ASSERT(window);
  1851   if (window) {
  1852     // mActiveWindows contains both windows that have requested device
  1853     // access and windows that are currently capturing media. We want
  1854     // to return only the latter. See bug 975177.
  1855     bool capturing = false;
  1856     if (aData) {
  1857       uint32_t length = aData->Length();
  1858       for (uint32_t i = 0; i < length; ++i) {
  1859         nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
  1860           aData->ElementAt(i);
  1861         if (listener->CapturingVideo() || listener->CapturingAudio()) {
  1862           capturing = true;
  1863           break;
  1868     if (capturing)
  1869       array->AppendElement(window);
  1871   return PL_DHASH_NEXT;
  1875 nsresult
  1876 MediaManager::GetActiveMediaCaptureWindows(nsISupportsArray **aArray)
  1878   MOZ_ASSERT(aArray);
  1879   nsISupportsArray *array;
  1880   nsresult rv = NS_NewISupportsArray(&array); // AddRefs
  1881   if (NS_FAILED(rv))
  1882     return rv;
  1884   mActiveWindows.EnumerateRead(WindowsHashToArrayFunc, array);
  1886   *aArray = array;
  1887   return NS_OK;
  1890 // XXX flags might be better...
  1891 struct CaptureWindowStateData {
  1892   bool *mVideo;
  1893   bool *mAudio;
  1894 };
  1896 static void
  1897 CaptureWindowStateCallback(MediaManager *aThis,
  1898                            uint64_t aWindowID,
  1899                            StreamListeners *aListeners,
  1900                            void *aData)
  1902   struct CaptureWindowStateData *data = (struct CaptureWindowStateData *) aData;
  1904   if (aListeners) {
  1905     auto length = aListeners->Length();
  1906     for (size_t i = 0; i < length; ++i) {
  1907       GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
  1909       if (listener->CapturingVideo()) {
  1910         *data->mVideo = true;
  1912       if (listener->CapturingAudio()) {
  1913         *data->mAudio = true;
  1920 NS_IMETHODIMP
  1921 MediaManager::MediaCaptureWindowState(nsIDOMWindow* aWindow, bool* aVideo,
  1922                                       bool* aAudio)
  1924   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1925   struct CaptureWindowStateData data;
  1926   data.mVideo = aVideo;
  1927   data.mAudio = aAudio;
  1929   *aVideo = false;
  1930   *aAudio = false;
  1932   nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
  1933   if (piWin) {
  1934     IterateWindowListeners(piWin, CaptureWindowStateCallback, &data);
  1936 #ifdef DEBUG
  1937   LOG(("%s: window %lld capturing %s %s", __FUNCTION__, piWin ? piWin->WindowID() : -1,
  1938        *aVideo ? "video" : "", *aAudio ? "audio" : ""));
  1939 #endif
  1940   return NS_OK;
  1943 // lets us do all sorts of things to the listeners
  1944 void
  1945 MediaManager::IterateWindowListeners(nsPIDOMWindow *aWindow,
  1946                                      WindowListenerCallback aCallback,
  1947                                      void *aData)
  1949   // Iterate the docshell tree to find all the child windows, and for each
  1950   // invoke the callback
  1951   nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
  1952   if (piWin) {
  1953     if (piWin->IsInnerWindow() || piWin->GetCurrentInnerWindow()) {
  1954       uint64_t windowID;
  1955       if (piWin->IsInnerWindow()) {
  1956         windowID = piWin->WindowID();
  1957       } else {
  1958         windowID = piWin->GetCurrentInnerWindow()->WindowID();
  1960       StreamListeners* listeners = GetActiveWindows()->Get(windowID);
  1961       // pass listeners so it can modify/delete the list
  1962       (*aCallback)(this, windowID, listeners, aData);
  1965     // iterate any children of *this* window (iframes, etc)
  1966     nsCOMPtr<nsIDocShell> docShell = piWin->GetDocShell();
  1967     if (docShell) {
  1968       int32_t i, count;
  1969       docShell->GetChildCount(&count);
  1970       for (i = 0; i < count; ++i) {
  1971         nsCOMPtr<nsIDocShellTreeItem> item;
  1972         docShell->GetChildAt(i, getter_AddRefs(item));
  1973         nsCOMPtr<nsPIDOMWindow> win = do_GetInterface(item);
  1975         if (win) {
  1976           IterateWindowListeners(win, aCallback, aData);
  1983 void
  1984 MediaManager::StopMediaStreams()
  1986   nsCOMPtr<nsISupportsArray> array;
  1987   GetActiveMediaCaptureWindows(getter_AddRefs(array));
  1988   uint32_t len;
  1989   array->Count(&len);
  1990   for (uint32_t i = 0; i < len; i++) {
  1991     nsCOMPtr<nsISupports> window;
  1992     array->GetElementAt(i, getter_AddRefs(window));
  1993     nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(window));
  1994     if (win) {
  1995       OnNavigation(win->WindowID());
  2000 // Can be invoked from EITHER MainThread or MSG thread
  2001 void
  2002 GetUserMediaCallbackMediaStreamListener::Invalidate()
  2005   nsRefPtr<MediaOperationRunnable> runnable;
  2006   // We can't take a chance on blocking here, so proxy this to another
  2007   // thread.
  2008   // Pass a ref to us (which is threadsafe) so it can query us for the
  2009   // source stream info.
  2010   runnable = new MediaOperationRunnable(MEDIA_STOP,
  2011                                         this, nullptr, nullptr,
  2012                                         mAudioSource, mVideoSource,
  2013                                         mFinished, mWindowID, nullptr);
  2014   mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
  2017 // Called from the MediaStreamGraph thread
  2018 void
  2019 GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph* aGraph)
  2021   mFinished = true;
  2022   Invalidate(); // we know it's been activated
  2023   NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, this));
  2026 // Called from the MediaStreamGraph thread
  2027 // this can be in response to our own RemoveListener() (via ::Remove()), or
  2028 // because the DOM GC'd the DOMLocalMediaStream/etc we're attached to.
  2029 void
  2030 GetUserMediaCallbackMediaStreamListener::NotifyRemoved(MediaStreamGraph* aGraph)
  2033     MutexAutoLock lock(mLock); // protect access to mRemoved
  2034     MM_LOG(("Listener removed by DOM Destroy(), mFinished = %d", (int) mFinished));
  2035     mRemoved = true;
  2037   if (!mFinished) {
  2038     NotifyFinished(aGraph);
  2042 NS_IMETHODIMP
  2043 GetUserMediaNotificationEvent::Run()
  2045   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  2046   // Make sure mStream is cleared and our reference to the DOMMediaStream
  2047   // is dropped on the main thread, no matter what happens in this method.
  2048   // Otherwise this object might be destroyed off the main thread,
  2049   // releasing DOMMediaStream off the main thread, which is not allowed.
  2050   nsRefPtr<DOMMediaStream> stream = mStream.forget();
  2052   nsString msg;
  2053   switch (mStatus) {
  2054   case STARTING:
  2055     msg = NS_LITERAL_STRING("starting");
  2056     stream->OnTracksAvailable(mOnTracksAvailableCallback.forget());
  2057     break;
  2058   case STOPPING:
  2059     msg = NS_LITERAL_STRING("shutdown");
  2060     if (mListener) {
  2061       mListener->SetStopped();
  2063     break;
  2066   nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
  2067   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
  2069   return MediaManager::NotifyRecordingStatusChange(window, msg, mIsAudio, mIsVideo);
  2072 } // namespace mozilla

mercurial