Bug 803799: Start gUM streams in Success callback; add MediaManager mutex r=anant,roc
authorRandell Jesup <rjesup@jesup.org>
Wed, 24 Oct 2012 19:21:15 -0400
changeset 111444 d6a3f003d316a4ee20c0010fdb67e3bc089618c3
parent 111443 b09e5e21012f6f165bda0b2dce4781add36c21d0
child 111445 0b3cc07a299a0e6a30dc6d6f38acdb1839361e84
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersanant, roc
bugs803799
milestone19.0a1
Bug 803799: Start gUM streams in Success callback; add MediaManager mutex r=anant,roc
content/media/webrtc/MediaEngineWebRTCAudio.cpp
content/media/webrtc/MediaEngineWebRTCVideo.cpp
dom/media/MediaManager.cpp
dom/media/MediaManager.h
--- a/content/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -134,16 +134,23 @@ MediaEngineWebRTCAudioSource::Stop()
   return NS_OK;
 }
 
 void
 MediaEngineWebRTCAudioSource::NotifyPull(MediaStreamGraph* aGraph,
                                          StreamTime aDesiredTime)
 {
   // Ignore - we push audio data
+#ifdef DEBUG
+  static TrackTicks mLastEndTime = 0;
+  TrackTicks target = TimeToTicksRoundUp(SAMPLE_FREQUENCY, aDesiredTime);
+  TrackTicks delta = target - mLastEndTime;
+  LOG(("Audio:NotifyPull: target %lu, delta %lu",(uint32_t) target, (uint32_t) delta));
+  mLastEndTime = target;
+#endif
 }
 
 nsresult
 MediaEngineWebRTCAudioSource::Snapshot(uint32_t aDuration, nsIDOMFile** aFile)
 {
    return NS_ERROR_NOT_IMPLEMENTED;
 }
 
--- a/content/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -246,32 +246,32 @@ MediaEngineWebRTCVideoSource::Start(Sour
   mSource = aStream;
   mTrackID = aID;
 
   mImageContainer = layers::LayerManager::CreateImageContainer();
 
   mSource->AddTrack(aID, USECS_PER_S, 0, new VideoSegment());
   mSource->AdvanceKnownTracksTime(STREAM_TIME_MAX);
   mLastEndTime = 0;
+  mState = kStarted;
 
   error = mViERender->AddRenderer(mCaptureIndex, webrtc::kVideoI420, (webrtc::ExternalRenderer*)this);
   if (error == -1) {
     return NS_ERROR_FAILURE;
   }
 
   error = mViERender->StartRender(mCaptureIndex);
   if (error == -1) {
     return NS_ERROR_FAILURE;
   }
 
   if (mViECapture->StartCapture(mCaptureIndex, mCapability) < 0) {
     return NS_ERROR_FAILURE;
   }
 
-  mState = kStarted;
   return NS_OK;
 }
 
 nsresult
 MediaEngineWebRTCVideoSource::Stop()
 {
   if (mState != kStarted) {
     return NS_ERROR_FAILURE;
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -57,19 +57,22 @@ public:
   Run()
   {
     // Only run if the window is still active.
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success(mSuccess);
     nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
 
-    WindowTable* activeWindows = MediaManager::Get()->GetActiveWindows();
-    if (activeWindows->Get(mWindowID)) {
-      error->OnError(mErrorMsg);
+    {
+      MutexAutoLock lock(MediaManager::Get()->GetMutex());
+      WindowTable* activeWindows = MediaManager::Get()->GetActiveWindows();
+      if (activeWindows->Get(mWindowID)) {
+        error->OnError(mErrorMsg);
+      }
     }
     return NS_OK;
   }
 
 private:
   already_AddRefed<nsIDOMGetUserMediaSuccessCallback> mSuccess;
   already_AddRefed<nsIDOMGetUserMediaErrorCallback> mError;
   const nsString mErrorMsg;
@@ -98,20 +101,23 @@ public:
   Run()
   {
     // Only run if the window is still active.
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success(mSuccess);
     nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
 
-    WindowTable* activeWindows = MediaManager::Get()->GetActiveWindows();
-    if (activeWindows->Get(mWindowID)) {
-      // XPConnect is a magical unicorn.
-      success->OnSuccess(mFile);
+    {
+      MutexAutoLock lock(MediaManager::Get()->GetMutex());
+      WindowTable* activeWindows = MediaManager::Get()->GetActiveWindows();
+      if (activeWindows->Get(mWindowID)) {
+        // XPConnect is a magical unicorn.
+        success->OnSuccess(mFile);
+      }
     }
     return NS_OK;
   }
 
 private:
   already_AddRefed<nsIDOMGetUserMediaSuccessCallback> mSuccess;
   already_AddRefed<nsIDOMGetUserMediaErrorCallback> mError;
   nsCOMPtr<nsIDOMFile> mFile;
@@ -204,17 +210,17 @@ MediaDevice::GetSource()
  * to the DOM with the stream. We also pass in the error callback so it can
  * be released correctly.
  *
  * All of this must be done on the main thread!
  *
  * Note that the various GetUserMedia Runnable classes currently allow for
  * two streams.  If we ever need to support getting more than two streams
  * at once, we could convert everything to nsTArray<nsRefPtr<blah> >'s,
- * though that would complicate the constructors some.  Currently the 
+ * though that would complicate the constructors some.  Currently the
  * GetUserMedia spec does not allow for more than 2 streams to be obtained in
  * one call, to simplify handling of constraints.
  */
 class GetUserMediaStreamRunnable : public nsRunnable
 {
 public:
   GetUserMediaStreamRunnable(
     already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
@@ -242,48 +248,64 @@ public:
     uint32_t hints = (mAudioSource ? nsDOMMediaStream::HINT_CONTENTS_AUDIO : 0);
     hints |= (mVideoSource ? nsDOMMediaStream::HINT_CONTENTS_VIDEO : 0);
 
     stream = nsDOMMediaStream::CreateInputStream(hints);
 
     nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
       (nsGlobalWindow::GetInnerWindowWithId(mWindowID));
     WindowTable* activeWindows = MediaManager::Get()->GetActiveWindows();
+    {
+      MutexAutoLock lock(MediaManager::Get()->GetMutex());
 
-    if (!stream) {
-      if (activeWindows->Get(mWindowID)) {
-        nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
-        LOG(("Returning error for getUserMedia() - no stream"));
-        error->OnError(NS_LITERAL_STRING("NO_STREAM"));
+      if (!stream) {
+        if (activeWindows->Get(mWindowID)) {
+          nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
+          LOG(("Returning error for getUserMedia() - no stream"));
+          error->OnError(NS_LITERAL_STRING("NO_STREAM"));
+        }
+        return NS_OK;
       }
-      return NS_OK;
     }
-
     if (window && window->GetExtantDoc()) {
       stream->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal());
     }
 
+    // Ensure there's a thread for gum to proxy to off main thread
+    nsIThread *mediaThread = MediaManager::GetThread();
+
     // Add our listener. We'll call Start() on the source when get a callback
     // that the MediaStream has started consuming. The listener is freed
     // when the page is invalidated (on navigation or close).
     GetUserMediaCallbackMediaStreamListener* listener =
-      new GetUserMediaCallbackMediaStreamListener(stream, mAudioSource,
+      new GetUserMediaCallbackMediaStreamListener(mediaThread, stream,
+                                                  mAudioSource,
                                                   mVideoSource);
     stream->GetStream()->AddListener(listener);
 
     // No need for locking because we always do this in the main thread.
     mListeners->AppendElement(listener);
 
+    // Dispatch to the media thread to ask it to start the sources,
+    // because that can take a while
+    nsRefPtr<MediaOperationRunnable> runnable(
+      new MediaOperationRunnable(MEDIA_START, stream,
+                                 mAudioSource, mVideoSource));
+    mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+
     // We're in the main thread, so no worries here either.
     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success(mSuccess);
     nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
 
-    if (activeWindows->Get(mWindowID)) {
-      LOG(("Returning success for getUserMedia()"));
-      success->OnSuccess(stream);
+    {
+      MutexAutoLock lock(MediaManager::Get()->GetMutex());
+      if (activeWindows->Get(mWindowID)) {
+        LOG(("Returning success for getUserMedia()"));
+        success->OnSuccess(stream);
+      }
     }
 
     return NS_OK;
   }
 
 private:
   already_AddRefed<nsIDOMGetUserMediaSuccessCallback> mSuccess;
   already_AddRefed<nsIDOMGetUserMediaErrorCallback> mError;
@@ -307,31 +329,31 @@ class GetUserMediaRunnable : public nsRu
 public:
   /**
    * The caller can choose to provide a MediaDevice as the last argument,
    * if one is not provided, a default device is automatically chosen.
    */
   GetUserMediaRunnable(bool aAudio, bool aVideo, bool aPicture,
     already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
-    StreamListeners* aListeners, uint64_t aWindowID, 
+    StreamListeners* aListeners, uint64_t aWindowID,
     MediaDevice* aAudioDevice, MediaDevice* aVideoDevice)
     : mAudio(aAudio)
     , mVideo(aVideo)
     , mPicture(aPicture)
     , mSuccess(aSuccess)
     , mError(aError)
     , mListeners(aListeners)
     , mWindowID(aWindowID)
     , mDeviceChosen(true)
     , mBackendChosen(false)
     {
       if (mAudio) {
         mAudioDevice = aAudioDevice;
-      } 
+      }
       if (mVideo) {
         mVideoDevice = aVideoDevice;
       }
     }
 
   GetUserMediaRunnable(bool aAudio, bool aVideo, bool aPicture,
     already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
@@ -754,73 +776,73 @@ MediaManager::GetUserMedia(bool aPrivile
       }
     }
   }
 #endif
 
   // Store the WindowID in a hash table and mark as active. The entry is removed
   // when this window is closed or navigated away from.
   uint64_t windowID = aWindow->WindowID();
-  StreamListeners* listeners = mActiveWindows.Get(windowID);
-  if (!listeners) {
-    listeners = new StreamListeners;
-    mActiveWindows.Put(windowID, listeners);
-  }
+  nsRefPtr<GetUserMediaRunnable> gUMRunnable;
+  {
+    MutexAutoLock lock(mMutex);
+    StreamListeners* listeners = mActiveWindows.Get(windowID);
+    if (!listeners) {
+      listeners = new StreamListeners;
+      mActiveWindows.Put(windowID, listeners);
+    }
+
+    // Developer preference for turning off permission check.
+    if (Preferences::GetBool("media.navigator.permission.disabled", false)) {
+      aPrivileged = true;
+    }
 
-  // Developer preference for turning off permission check.
-  if (Preferences::GetBool("media.navigator.permission.disabled", false)) {
-    aPrivileged = true;
+    /**
+     * Pass runnables along to GetUserMediaRunnable so it can add the
+     * MediaStreamListener to the runnable list. The last argument can
+     * optionally be a MediaDevice object, which should provided if one was
+     * selected by the user via the UI, or was provided by privileged code
+     * via the device: attribute via nsIMediaStreamOptions.
+     *
+     * If a fake stream was requested, we force the use of the default backend.
+     */
+    if (fake) {
+      // Fake stream from default backend.
+      gUMRunnable = new GetUserMediaRunnable(
+        audio, video, onSuccess.forget(), onError.forget(), listeners,
+        windowID, new MediaEngineDefault()
+                                             );
+    } else if (audiodevice || videodevice) {
+      // Stream from provided device.
+      gUMRunnable = new GetUserMediaRunnable(
+        audio, video, picture, onSuccess.forget(), onError.forget(), listeners,
+        windowID,
+        static_cast<MediaDevice*>(audiodevice.get()),
+        static_cast<MediaDevice*>(videodevice.get())
+                                             );
+    } else {
+      // Stream from default device from WebRTC backend.
+      gUMRunnable = new GetUserMediaRunnable(
+        audio, video, picture, onSuccess.forget(), onError.forget(), listeners,
+        windowID
+                                             );
+    }
   }
 
-  /**
-   * Pass runnables along to GetUserMediaRunnable so it can add the
-   * MediaStreamListener to the runnable list. The last argument can
-   * optionally be a MediaDevice object, which should provided if one was
-   * selected by the user via the UI, or was provided by privileged code
-   * via the device: attribute via nsIMediaStreamOptions.
-   *
-   * If a fake stream was requested, we force the use of the default backend.
-   */
-  nsRefPtr<GetUserMediaRunnable> gUMRunnable;
-  if (fake) {
-    // Fake stream from default backend.
-    gUMRunnable = new GetUserMediaRunnable(
-      audio, video, onSuccess.forget(), onError.forget(), listeners,
-      windowID, new MediaEngineDefault()
-    );
-  } else if (audiodevice || videodevice) {
-    // Stream from provided device.
-    gUMRunnable = new GetUserMediaRunnable(
-      audio, video, picture, onSuccess.forget(), onError.forget(), listeners,
-      windowID, 
-      static_cast<MediaDevice*>(audiodevice.get()),
-      static_cast<MediaDevice*>(videodevice.get())
-    );
-  } else {
-    // Stream from default device from WebRTC backend.
-    gUMRunnable = new GetUserMediaRunnable(
-      audio, video, picture, onSuccess.forget(), onError.forget(), listeners,
-      windowID
-    );
-  }
 
 #ifdef ANDROID
   if (picture) {
     // ShowFilePickerForMimeType() must run on the Main Thread! (on Android)
     NS_DispatchToMainThread(gUMRunnable);
   }
   // XXX No support for Audio or Video in Android yet
 #else
   // XXX No full support for picture in Desktop yet (needs proper UI)
   if (aPrivileged || fake) {
-    if (!mMediaThread) {
-      nsresult rv = NS_NewThread(getter_AddRefs(mMediaThread));
-      NS_ENSURE_SUCCESS(rv, rv);
-      LOG(("New Media thread for gum"));
-    }
+    (void) MediaManager::GetThread();
     mMediaThread->Dispatch(gUMRunnable, NS_DISPATCH_NORMAL);
   } else {
     // Ask for user permission, and dispatch runnable (or not) when a response
     // is received via an observer notification. Each call is paired with its
     // runnable by a GUID.
     nsresult rv;
     nsCOMPtr<nsIUUIDGenerator> uuidgen =
       do_GetService("@mozilla.org/uuid-generator;1", &rv);
@@ -831,17 +853,20 @@ MediaManager::GetUserMedia(bool aPrivile
     rv = uuidgen->GenerateUUIDInPlace(&id);
     NS_ENSURE_SUCCESS(rv, rv);
 
     char buffer[NSID_LENGTH];
     id.ToProvidedString(buffer);
     NS_ConvertUTF8toUTF16 callID(buffer);
 
     // Store the current callback.
-    mActiveCallbacks.Put(callID, gUMRunnable);
+    {
+      MutexAutoLock lock(mMutex);
+      mActiveCallbacks.Put(callID, gUMRunnable);
+    }
 
     // Construct JSON structure with both the windowID and the callID.
     nsAutoString data;
     data.Append(NS_LITERAL_STRING("{\"windowID\":"));
 
     // Convert window ID to string.
     char windowBuffer[32];
     PR_snprintf(windowBuffer, 32, "%llu", aWindow->GetOuterWindow()->WindowID());
@@ -907,68 +932,71 @@ MediaManager::GetActiveWindows()
   return &mActiveWindows;
 }
 
 void
 MediaManager::OnNavigation(uint64_t aWindowID)
 {
   // Invalidate this window. The runnables check this value before making
   // a call to content.
-  StreamListeners* listeners = mActiveWindows.Get(aWindowID);
-  if (!listeners) {
-    return;
-  }
+  {
+    MutexAutoLock lock(mMutex);
+    StreamListeners* listeners = mActiveWindows.Get(aWindowID);
+    if (!listeners) {
+      return;
+    }
 
-  uint32_t length = listeners->Length();
-  for (uint32_t i = 0; i < length; i++) {
-    nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-      listeners->ElementAt(i);
-    listener->Invalidate();
-    listener = nullptr;
+    uint32_t length = listeners->Length();
+    for (uint32_t i = 0; i < length; i++) {
+      nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
+        listeners->ElementAt(i);
+      listener->Invalidate();
+      listener = nullptr;
+    }
+    listeners->Clear();
+
+    mActiveWindows.Remove(aWindowID);
   }
-  listeners->Clear();
-
-  mActiveWindows.Remove(aWindowID);
 }
 
 nsresult
 MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
   const PRUnichar* aData)
 {
   NS_ASSERTION(NS_IsMainThread(), "Observer invoked off the main thread");
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 
   if (!strcmp(aTopic, "xpcom-shutdown")) {
     obs->RemoveObserver(this, "xpcom-shutdown");
     obs->RemoveObserver(this, "getUserMedia:response:allow");
     obs->RemoveObserver(this, "getUserMedia:response:deny");
 
     // Close off any remaining active windows.
-    mActiveWindows.Clear();
-    mActiveCallbacks.Clear();
-    sSingleton = nullptr;
+    {
+      MutexAutoLock lock(mMutex);
+      mActiveWindows.Clear();
+      mActiveCallbacks.Clear();
+      sSingleton = nullptr;
+    }
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "getUserMedia:response:allow")) {
     nsString key(aData);
     nsRefPtr<nsRunnable> runnable;
-    if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
-      return NS_OK;
+    {
+      MutexAutoLock lock(mMutex);
+      if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
+        return NS_OK;
+      }
     }
 
     // Reuse the same thread to save memory.
-    if (!mMediaThread) {
-      LOG(("New Media thread for gum on allow"));
-      nsresult rv = NS_NewThread(getter_AddRefs(mMediaThread));
-      NS_ENSURE_SUCCESS(rv, rv);
-    } else {
-      LOG(("Reused Media thread for gum on allow"));
-    }
+    (void) MediaManager::GetThread();
 
     if (aSubject) {
       // A particular device was chosen by the user.
       // NOTE: does not allow setting a device to null; assumes nullptr
       nsCOMPtr<nsIMediaDevice> device = do_QueryInterface(aSubject);
       if (device) {
         GetUserMediaRunnable* gUMRunnable =
           static_cast<GetUserMediaRunnable*>(runnable.get());
@@ -980,29 +1008,34 @@ MediaManager::Observe(nsISupports* aSubj
           gUMRunnable->SetAudioDevice(static_cast<MediaDevice*>(device.get()));
         } else {
           NS_WARNING("Unknown device type in getUserMedia");
         }
       }
     }
 
     mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
-    mActiveCallbacks.Remove(key);
+    {
+      MutexAutoLock lock(mMutex);
+      mActiveCallbacks.Remove(key);
+    }
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "getUserMedia:response:deny")) {
     nsString key(aData);
     nsRefPtr<nsRunnable> runnable;
-    if (mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
-      GetUserMediaRunnable* gUMRunnable =
+    {
+      MutexAutoLock lock(mMutex);
+      if (mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
+        GetUserMediaRunnable* gUMRunnable =
           static_cast<GetUserMediaRunnable*>(runnable.get());
-      gUMRunnable->Denied();
-      mActiveCallbacks.Remove(key);
+        gUMRunnable->Denied();
+        mActiveCallbacks.Remove(key);
+      }
     }
-
     return NS_OK;
   }
 
   return NS_OK;
 }
 
 } // namespace mozilla
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -60,89 +60,162 @@ class GetUserMediaNotificationEvent: pub
       }
       return NS_OK;
     }
 
   protected:
     GetUserMediaStatus mStatus;
 };
 
+typedef enum {
+  MEDIA_START,
+  MEDIA_STOP,
+  MEDIA_RELEASE
+} MediaOperation;
+
+// Generic class for running long media operations off the main thread, and
+// then (because nsDOMMediaStreams aren't threadsafe), re-sends itseld to
+// MainThread to release mStream.  This is part of the reason we use an
+// operation type - we can change it to repost the runnable to MainThread
+// to do operations with the nsDOMMediaStreams, while we can't assign or
+// copy a nsRefPtr to a nsDOMMediaStream
+class MediaOperationRunnable : public nsRunnable
+{
+public:
+  MediaOperationRunnable(MediaOperation aType,
+    nsDOMMediaStream* aStream,
+    MediaEngineSource* aAudioSource,
+    MediaEngineSource* aVideoSource)
+    : mType(aType)
+    , mAudioSource(aAudioSource)
+    , mVideoSource(aVideoSource)
+    , mStream(aStream)
+    {}
+
+  MediaOperationRunnable(MediaOperation aType,
+    SourceMediaStream* aStream,
+    MediaEngineSource* aAudioSource,
+    MediaEngineSource* aVideoSource)
+    : mType(aType)
+    , mAudioSource(aAudioSource)
+    , mVideoSource(aVideoSource)
+    , mStream(nullptr)
+    , mSourceStream(aStream)
+    {}
+
+  NS_IMETHOD
+  Run()
+  {
+    // No locking between these is required as all the callbacks (other
+    // than MEDIA_RELEASE) for the same MediaStream will occur on the same
+    // thread.
+    if (mStream) {
+      mSourceStream = mStream->GetStream()->AsSourceStream();
+    }
+    switch (mType) {
+      case MEDIA_START:
+        {
+          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
+          nsresult rv;
+
+          mSourceStream->SetPullEnabled(true);
+
+          if (mAudioSource) {
+            rv = mAudioSource->Start(mSourceStream, kAudioTrack);
+            if (NS_FAILED(rv)) {
+              MM_LOG(("Starting audio failed, rv=%d",rv));
+            }
+          }
+          if (mVideoSource) {
+            rv = mVideoSource->Start(mSourceStream, kVideoTrack);
+            if (NS_FAILED(rv)) {
+              MM_LOG(("Starting video failed, rv=%d",rv));
+            }
+          }
+
+          MM_LOG(("started all sources"));
+          nsCOMPtr<GetUserMediaNotificationEvent> event =
+            new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING);
+
+          NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+        }
+        break;
+
+      case MEDIA_STOP:
+        {
+          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
+          if (mAudioSource) {
+            mAudioSource->Stop();
+            mAudioSource->Deallocate();
+          }
+          if (mVideoSource) {
+            mVideoSource->Stop();
+            mVideoSource->Deallocate();
+          }
+          // Do this after stopping all tracks with EndTrack()
+          mSourceStream->Finish();
+
+          nsCOMPtr<GetUserMediaNotificationEvent> event =
+            new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STOPPING);
+
+          NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+        }
+        break;
+      case MEDIA_RELEASE:
+        // We go to MainThread to die
+        break;
+    }
+    if (mType != MEDIA_RELEASE) {
+      // nsDOMMediaStreams aren't thread-safe... sigh.
+      mType = MEDIA_RELEASE;
+      NS_DispatchToMainThread(this);
+    }
+    return NS_OK;
+  }
+
+private:
+  MediaOperation mType;
+  nsRefPtr<MediaEngineSource> mAudioSource;
+  nsRefPtr<MediaEngineSource> mVideoSource;
+  nsCOMPtr<nsDOMMediaStream> mStream;
+  SourceMediaStream *mSourceStream;
+};
+
 /**
  * This class is an implementation of MediaStreamListener. This is used
  * to Start() and Stop() the underlying MediaEngineSource when MediaStreams
  * are assigned and deassigned in content.
  */
 class GetUserMediaCallbackMediaStreamListener : public MediaStreamListener
 {
 public:
-  GetUserMediaCallbackMediaStreamListener(nsDOMMediaStream* aStream,
+  GetUserMediaCallbackMediaStreamListener(nsIThread *aThread,
+    nsDOMMediaStream* aStream,
     MediaEngineSource* aAudioSource,
     MediaEngineSource* aVideoSource)
-    : mAudioSource(aAudioSource)
+    : mMediaThread(aThread)
+    , mAudioSource(aAudioSource)
     , mVideoSource(aVideoSource)
     , mStream(aStream)
     , mValid(true) {}
 
   void
   Invalidate()
   {
-    if (!mValid) {
-      return;
-    }
-
-    mValid = false;
-    if (mAudioSource) {
-      mAudioSource->Stop();
-      mAudioSource->Deallocate();
-    }
-    if (mVideoSource) {
-      mVideoSource->Stop();
-      mVideoSource->Deallocate();
-    }
-    // Do this after stopping all tracks with EndTrack()
-    mStream->GetStream()->AsSourceStream()->Finish();
-
-    nsCOMPtr<GetUserMediaNotificationEvent> event =
-      new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STOPPING);
-
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-  }
+    nsRefPtr<MediaOperationRunnable> runnable;
 
-  void
-  NotifyConsumptionChanged(MediaStreamGraph* aGraph, Consumption aConsuming)
-  {
-    if (aConsuming == CONSUMED) {
-      nsresult rv;
-
-      SourceMediaStream* stream = mStream->GetStream()->AsSourceStream();
-      stream->SetPullEnabled(true);
+    // We can't take a chance on blocking here, so proxy this to another
+    // thread.
+    // XXX FIX! I'm cheating and passing a raw pointer to the sourcestream
+    // which is valid as long as the mStream pointer here is.  Need a better solution.
+    runnable = new MediaOperationRunnable(MEDIA_STOP, 
+                                          mStream->GetStream()->AsSourceStream(),
+                                          mAudioSource, mVideoSource);
+    mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
 
-      if (mAudioSource) {
-        rv = mAudioSource->Start(stream, kAudioTrack);
-        if (NS_FAILED(rv)) {
-          MM_LOG(("Starting audio failed, rv=%d",rv));
-        }
-      }
-      if (mVideoSource) {
-        rv = mVideoSource->Start(stream, kVideoTrack);
-        if (NS_FAILED(rv)) {
-          MM_LOG(("Starting video failed, rv=%d",rv));
-        }
-      }
-
-      MM_LOG(("started all sources"));
-      nsCOMPtr<GetUserMediaNotificationEvent> event =
-        new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING);
-
-      NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-      return;
-    }
-
-    // NOT_CONSUMED
-    Invalidate();
     return;
   }
 
   // Proxy NotifyPull() to sources
   void
   NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime)
   {
     // Currently audio sources ignore NotifyPull, but they could
@@ -151,16 +224,17 @@ public:
       mAudioSource->NotifyPull(aGraph, aDesiredTime);
     }
     if (mVideoSource) {
       mVideoSource->NotifyPull(aGraph, aDesiredTime);
     }
   }
 
 private:
+  nsCOMPtr<nsIThread> mMediaThread;
   nsRefPtr<MediaEngineSource> mAudioSource;
   nsRefPtr<MediaEngineSource> mVideoSource;
   nsCOMPtr<nsDOMMediaStream> mStream;
   bool mValid;
 };
 
 typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
 typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
@@ -199,16 +273,27 @@ public:
 
       nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
       obs->AddObserver(sSingleton, "xpcom-shutdown", false);
       obs->AddObserver(sSingleton, "getUserMedia:response:allow", false);
       obs->AddObserver(sSingleton, "getUserMedia:response:deny", false);
     }
     return sSingleton;
   }
+  static Mutex& GetMutex() {
+    return Get()->mMutex;
+  }
+  static nsIThread* GetThread() {
+    MutexAutoLock lock(Get()->mMutex); // only need to call Get() once
+    if (!sSingleton->mMediaThread) {
+      NS_NewThread(getter_AddRefs(sSingleton->mMediaThread));
+      MM_LOG(("New Media thread for gum"));
+    }
+    return sSingleton->mMediaThread;
+  }
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   MediaEngine* GetBackend();
   WindowTable* GetActiveWindows();
 
   nsresult GetUserMedia(bool aPrivileged, nsPIDOMWindow* aWindow,
@@ -218,27 +303,29 @@ public:
   nsresult GetUserMediaDevices(nsPIDOMWindow* aWindow,
     nsIGetUserMediaDevicesSuccessCallback* onSuccess,
     nsIDOMGetUserMediaErrorCallback* onError);
   void OnNavigation(uint64_t aWindowID);
 
 private:
   // Make private because we want only one instance of this class
   MediaManager()
-  : mBackend(nullptr)
+  : mMutex("mozilla::MediaManager")
+  , mBackend(nullptr)
   , mMediaThread(nullptr) {
     mActiveWindows.Init();
     mActiveCallbacks.Init();
   };
-  MediaManager(MediaManager const&) {};
 
   ~MediaManager() {
     delete mBackend;
   };
 
+  Mutex mMutex;
+  // protected with mMutex:
   MediaEngine* mBackend;
   nsCOMPtr<nsIThread> mMediaThread;
   WindowTable mActiveWindows;
   nsRefPtrHashtable<nsStringHashKey, nsRunnable> mActiveCallbacks;
 
   static nsRefPtr<MediaManager> sSingleton;
 };