Bug 866514. Part 2: Delay delivering getUserMedia stream result until the DOM object has asynchronously acquired the desired tracks. r=jesup
authorRobert O'Callahan <robert@ocallahan.org>
Fri, 03 May 2013 17:07:37 +1200
changeset 141822 f110d0270f8e2757325d35f31567cf15a3e6b069
parent 141821 4609a4efeb3941bb2e4549bb054d792a31808c0b
child 141823 be3cef4b69e356da561383b76fe094c8933fc653
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup
bugs866514
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 866514. Part 2: Delay delivering getUserMedia stream result until the DOM object has asynchronously acquired the desired tracks. r=jesup
dom/media/MediaManager.cpp
dom/media/MediaManager.h
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -321,39 +321,75 @@ public:
     , mAudioSource(aAudioSource)
     , mVideoSource(aVideoSource)
     , mWindowID(aWindowID)
     , mListener(aListener)
     , mManager(MediaManager::GetInstance()) {}
 
   ~GetUserMediaStreamRunnable() {}
 
+  class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback
+  {
+  public:
+    TracksAvailableCallback(MediaManager* aManager,
+                            nsIDOMGetUserMediaSuccessCallback* aSuccess,
+                            uint64_t aWindowID,
+                            DOMMediaStream* aStream)
+      : mWindowID(aWindowID), mSuccess(aSuccess), mManager(aManager),
+        mStream(aStream) {}
+    virtual void NotifyTracksAvailable(DOMMediaStream* aStream) MOZ_OVERRIDE
+    {
+      // We're in the main thread, so no worries here.
+      if (!(mManager->IsWindowStillActive(mWindowID))) {
+        return;
+      }
+
+      // This is safe since we're on main-thread, and the windowlist can only
+      // be invalidated from the main-thread (see OnNavigation)
+      LOG(("Returning success for getUserMedia()"));
+      mSuccess->OnSuccess(aStream);
+    }
+    uint64_t mWindowID;
+    nsRefPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
+    nsRefPtr<MediaManager> mManager;
+    // Keep the DOMMediaStream alive until the NotifyTracksAvailable callback
+    // has fired, otherwise we might immediately destroy the DOMMediaStream and
+    // shut down the underlying MediaStream prematurely.
+    // This creates a cycle which is broken when NotifyTracksAvailable
+    // is fired (which will happen unless the browser shuts down,
+    // since we only add this callback when we've successfully appended
+    // the desired tracks in the MediaStreamGraph) or when
+    // DOMMediaStream::NotifyMediaStreamGraphShutdown is called.
+    nsRefPtr<DOMMediaStream> mStream;
+  };
+
   NS_IMETHOD
   Run()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
       (nsGlobalWindow::GetInnerWindowWithId(mWindowID));
 
     // We're on main-thread, and the windowlist can only
     // be invalidated from the main-thread (see OnNavigation)
     StreamListeners* listeners = mManager->GetWindowListeners(mWindowID);
     if (!listeners || !window || !window->GetExtantDoc()) {
       // This window is no longer live.  mListener has already been removed
       return NS_OK;
     }
 
     // Create a media stream.
-    uint32_t hints = (mAudioSource ? DOMMediaStream::HINT_CONTENTS_AUDIO : 0);
-    hints |= (mVideoSource ? DOMMediaStream::HINT_CONTENTS_VIDEO : 0);
+    DOMMediaStream::TrackTypeHints hints =
+      (mAudioSource ? DOMMediaStream::HINT_CONTENTS_AUDIO : 0) |
+      (mVideoSource ? DOMMediaStream::HINT_CONTENTS_VIDEO : 0);
 
     nsRefPtr<nsDOMUserMediaStream> trackunion =
       nsDOMUserMediaStream::CreateTrackUnionStream(window, hints);
     if (!trackunion) {
-      nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
+      nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
       LOG(("Returning error for getUserMedia() - no stream"));
       error->OnError(NS_LITERAL_STRING("NO_STREAM"));
       return NS_OK;
     }
 
     MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
     nsRefPtr<SourceMediaStream> stream = gm->CreateSourceStream(nullptr);
 
@@ -367,21 +403,27 @@ public:
     trackunion->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal());
 
     // The listener was added at the begining in an inactive state.
     // Activate 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).
     mListener->Activate(stream.forget(), mAudioSource, mVideoSource);
 
+    TracksAvailableCallback* tracksAvailableCallback =
+      new TracksAvailableCallback(mManager, mSuccess, mWindowID, trackunion);
+
     // Dispatch to the media thread to ask it to start the sources,
-    // because that can take a while
+    // because that can take a while.
+    // Pass ownership of trackunion to the MediaOperationRunnable
+    // to ensure it's kept alive until the MediaOperationRunnable runs (at least).
     nsIThread *mediaThread = MediaManager::GetThread();
     nsRefPtr<MediaOperationRunnable> runnable(
-      new MediaOperationRunnable(MEDIA_START, mListener,
+      new MediaOperationRunnable(MEDIA_START, mListener, trackunion,
+                                 tracksAvailableCallback,
                                  mAudioSource, mVideoSource, false));
     mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
 
 #ifdef MOZ_WEBRTC
     // Right now these configs are only of use if webrtc is available
     nsresult rv;
     nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
     if (NS_SUCCEEDED(rv)) {
@@ -402,34 +444,24 @@ public:
 
         mListener->AudioConfig(aec_on, (uint32_t) aec,
                                agc_on, (uint32_t) agc,
                                noise_on, (uint32_t) noise);
       }
     }
 #endif
 
-    // We're in the main thread, so no worries here either.
-    nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success(mSuccess);
-    nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
-
-    if (!(mManager->IsWindowStillActive(mWindowID))) {
-      return NS_OK;
-    }
-    // This is safe since we're on main-thread, and the windowlist can only
-    // be invalidated from the main-thread (see OnNavigation)
-    LOG(("Returning success for getUserMedia()"));
-    success->OnSuccess(static_cast<nsIDOMLocalMediaStream*>(trackunion));
-
+    // We won't need mError now.
+    mError = nullptr;
     return NS_OK;
   }
 
 private:
-  already_AddRefed<nsIDOMGetUserMediaSuccessCallback> mSuccess;
-  already_AddRefed<nsIDOMGetUserMediaErrorCallback> mError;
+  nsRefPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
+  nsRefPtr<nsIDOMGetUserMediaErrorCallback> mError;
   nsRefPtr<MediaEngineSource> mAudioSource;
   nsRefPtr<MediaEngineSource> mVideoSource;
   uint64_t mWindowID;
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
 /**
@@ -1493,17 +1525,18 @@ GetUserMediaCallbackMediaStreamListener:
 {
 
   nsRefPtr<MediaOperationRunnable> runnable;
   // We can't take a chance on blocking here, so proxy this to another
   // thread.
   // Pass a ref to us (which is threadsafe) so it can query us for the
   // source stream info.
   runnable = new MediaOperationRunnable(MEDIA_STOP,
-                                        this, mAudioSource, mVideoSource,
+                                        this, nullptr, nullptr,
+                                        mAudioSource, mVideoSource,
                                         mFinished);
   mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
 }
 
 // Called from the MediaStreamGraph thread
 void
 GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph* aGraph)
 {
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -17,16 +17,17 @@
 #include "nsIPrefBranch.h"
 
 #include "nsPIDOMWindow.h"
 #include "nsIDOMNavigatorUserMedia.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/StaticPtr.h"
 #include "prlog.h"
+#include "DOMMediaStream.h"
 
 #ifdef MOZ_WEBRTC
 #include "mtransport/runnable_utils.h"
 #endif
 
 #ifdef MOZ_WIDGET_GONK
 #include "DOMCameraManager.h"
 #endif
@@ -195,32 +196,36 @@ class GetUserMediaNotificationEvent: pub
     enum GetUserMediaStatus {
       STARTING,
       STOPPING
     };
     GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener,
                                   GetUserMediaStatus aStatus)
     : mListener(aListener), mStatus(aStatus) {}
 
-    GetUserMediaNotificationEvent(GetUserMediaStatus aStatus)
-    : mListener(nullptr), mStatus(aStatus) {}
+    GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
+                                  already_AddRefed<DOMMediaStream> aStream,
+                                  DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback)
+    : mStream(aStream), mOnTracksAvailableCallback(aOnTracksAvailableCallback),
+      mStatus(aStatus) {}
 
     NS_IMETHOD
     Run()
     {
       NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
       nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
       if (!obs) {
         NS_WARNING("Could not get the Observer service for GetUserMedia recording notification.");
         return NS_ERROR_FAILURE;
       }
       nsString msg;
       switch (mStatus) {
         case STARTING:
           msg = NS_LITERAL_STRING("starting");
+          mStream->OnTracksAvailable(mOnTracksAvailableCallback.forget());
           break;
         case STOPPING:
           msg = NS_LITERAL_STRING("shutdown");
           if (mListener) {
             mListener->SetStopped();
           }
           break;
       }
@@ -232,16 +237,18 @@ class GetUserMediaNotificationEvent: pub
       if (XRE_GetProcessType() != GeckoProcessType_Default) {
         unused << mozilla::dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(msg);
       }
       return NS_OK;
     }
 
   protected:
     nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
+    nsRefPtr<DOMMediaStream> mStream;
+    nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
     GetUserMediaStatus mStatus;
 };
 
 typedef enum {
   MEDIA_START,
   MEDIA_STOP
 } MediaOperation;
 
@@ -249,20 +256,24 @@ typedef enum {
 // thread, and then (because nsDOMMediaStreams aren't threadsafe),
 // ProxyReleases mStream since it's cycle collected.
 class MediaOperationRunnable : public nsRunnable
 {
 public:
   // so we can send Stop without AddRef()ing from the MSG thread
   MediaOperationRunnable(MediaOperation aType,
     GetUserMediaCallbackMediaStreamListener* aListener,
+    DOMMediaStream* aStream,
+    DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
     MediaEngineSource* aAudioSource,
     MediaEngineSource* aVideoSource,
     bool aNeedsFinish)
     : mType(aType)
+    , mStream(aStream)
+    , mOnTracksAvailableCallback(aOnTracksAvailableCallback)
     , mAudioSource(aAudioSource)
     , mVideoSource(aVideoSource)
     , mListener(aListener)
     , mFinish(aNeedsFinish)
     {}
 
   ~MediaOperationRunnable()
   {
@@ -281,34 +292,44 @@ public:
     switch (mType) {
       case MEDIA_START:
         {
           NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
           nsresult rv;
 
           source->SetPullEnabled(true);
 
+          DOMMediaStream::TrackTypeHints expectedTracks = 0;
           if (mAudioSource) {
             rv = mAudioSource->Start(source, kAudioTrack);
-            if (NS_FAILED(rv)) {
+            if (NS_SUCCEEDED(rv)) {
+              expectedTracks |= DOMMediaStream::HINT_CONTENTS_AUDIO;
+            } else {
               MM_LOG(("Starting audio failed, rv=%d",rv));
             }
           }
           if (mVideoSource) {
             rv = mVideoSource->Start(source, kVideoTrack);
-            if (NS_FAILED(rv)) {
+            if (NS_SUCCEEDED(rv)) {
+              expectedTracks |= DOMMediaStream::HINT_CONTENTS_VIDEO;
+            } else {
               MM_LOG(("Starting video failed, rv=%d",rv));
             }
           }
 
+          mOnTracksAvailableCallback->SetExpectedTracks(expectedTracks);
+
           MM_LOG(("started all sources"));
+          // Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent,
+          // because mOnTracksAvailableCallback needs to be added to mStream
+          // on the main thread.
           nsRefPtr<GetUserMediaNotificationEvent> event =
-            new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING);
-
-
+            new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING,
+                                              mStream.forget(),
+                                              mOnTracksAvailableCallback.forget());
           NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
         }
         break;
 
       case MEDIA_STOP:
         {
           NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
           if (mAudioSource) {
@@ -334,16 +355,18 @@ public:
         MOZ_ASSERT(false,"invalid MediaManager operation");
         break;
     }
     return NS_OK;
   }
 
 private:
   MediaOperation mType;
+  nsRefPtr<DOMMediaStream> mStream;
+  nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
   nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe
   nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
   bool mFinish;
 };
 
 typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
 typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;