Bug 912342 - get Promise out. r=jesup
authorJan-Ivar Bruaroey <jib@mozilla.com>
Sun, 20 Sep 2015 10:04:51 -0400
changeset 294583 1ff3901d09898110232f23b02d989c7c152aa1ad
parent 294582 6124439438d464ef26de8d5970006a94403a028e
child 294584 fa5be14d26e5adf896324d91b73cc628bf18b8db
push id5619
push userj.parkouss@gmail.com
push dateMon, 21 Sep 2015 17:26:04 +0000
reviewersjesup
bugs912342
milestone43.0a1
Bug 912342 - get Promise out. r=jesup
dom/media/MediaManager.cpp
dom/media/MediaManager.h
dom/media/tests/mochitest/test_getUserMedia_constraints.html
dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -328,42 +328,16 @@ public:
                                               mVideoDevice != nullptr,
                                               mWindowID);
           // event must always be released on mainthread due to the JS callbacks
           // in the TracksAvailableCallback
           NS_DispatchToMainThread(event);
         }
         break;
 
-      case MEDIA_APPLYCONSTRAINTS_TRACK:
-        {
-          nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
-
-          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
-          if (mAudioDevice) {
-            mAudioDevice->Restart(mConstraints, mgr->mPrefs);
-          }
-          if (mVideoDevice) {
-            mVideoDevice->Restart(mConstraints, mgr->mPrefs);
-          }
-
-          // Need to dispatch something back to main to resolve promise
-          // redo this with pledge?
-          nsIRunnable *event =
-            new GetUserMediaNotificationEvent(mListener,
-                                              GetUserMediaNotificationEvent::APPLIED_CONSTRAINTS,
-                                              mAudioDevice != nullptr,
-                                              mVideoDevice != nullptr,
-                                              mWindowID);
-          // event must always be released on mainthread due to the JS callbacks
-          // in the TracksAvailableCallback
-          NS_DispatchToMainThread(event);
-        }
-        break;
-
       case MEDIA_DIRECT_LISTENERS:
         {
           NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
           if (mVideoDevice) {
             mVideoDevice->GetSource()->SetDirectListeners(mBool);
           }
         }
         break;
@@ -721,44 +695,68 @@ public:
   virtual void StopTrack(TrackID aTrackID) override
   {
     if (mSourceStream) {
       mSourceStream->EndTrack(aTrackID);
       // We could override NotifyMediaStreamTrackEnded(), and maybe should, but it's
       // risky to do late in a release since that will affect all track ends, and not
       // just StopTrack()s.
       if (GetDOMTrackFor(aTrackID)) {
-        mListener->StopTrack(aTrackID, !!GetDOMTrackFor(aTrackID)->AsAudioStreamTrack());
+        mListener->StopTrack(aTrackID,
+                             !!GetDOMTrackFor(aTrackID)->AsAudioStreamTrack());
       } else {
-        LOG(("StopTrack(%d) on non-existant track", aTrackID));
+        LOG(("StopTrack(%d) on non-existent track", aTrackID));
       }
     }
   }
 
   virtual already_AddRefed<Promise>
   ApplyConstraintsToTrack(TrackID aTrackID,
                           const MediaTrackConstraints& aConstraints,
                           ErrorResult &aRv) override
   {
-    if (mSourceStream) {
-      nsRefPtr<dom::MediaStreamTrack> track = GetDOMTrackFor(aTrackID);
-      if (track) {
-        mListener->ApplyConstraintsToTrack(aTrackID,
-                                           !!track->AsAudioStreamTrack(),
-                                           aConstraints);
-      } else {
-        LOG(("ApplyConstraintsToTrack(%d) on non-existant track", aTrackID));
-      }
-    }
-
     nsPIDOMWindow* window = static_cast<nsPIDOMWindow*>(mWindow.get());
     nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
-    nsRefPtr<Promise> p = Promise::Create(go, aRv);
-    p->MaybeResolve(false);
-    return p.forget();
+    nsRefPtr<Promise> promise = Promise::Create(go, aRv);
+
+    if (sInShutdown) {
+      nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
+          NS_LITERAL_STRING("AbortError"),
+          NS_LITERAL_STRING("In shutdown"));
+      promise->MaybeReject(error);
+      return promise.forget();
+    }
+    if (!mSourceStream) {
+      nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
+          NS_LITERAL_STRING("InternalError"),
+          NS_LITERAL_STRING("No stream."));
+      promise->MaybeReject(error);
+      return promise.forget();
+    }
+
+    nsRefPtr<dom::MediaStreamTrack> track = GetDOMTrackFor(aTrackID);
+    if (!track) {
+      LOG(("ApplyConstraintsToTrack(%d) on non-existent track", aTrackID));
+      nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
+          NS_LITERAL_STRING("InternalError"),
+          NS_LITERAL_STRING("No track."));
+      promise->MaybeReject(error);
+      return promise.forget();
+    }
+
+    typedef media::Pledge<bool, MediaStreamError*> PledgeVoid;
+
+    nsRefPtr<PledgeVoid> p = mListener->ApplyConstraintsToTrack(window,
+        aTrackID, !!track->AsAudioStreamTrack(), aConstraints);
+    p->Then([promise](bool& aDummy) mutable {
+      promise->MaybeResolve(false);
+    }, [promise](MediaStreamError*& reason) mutable {
+      promise->MaybeReject(reason);
+    });
+    return promise.forget();
   }
 
 #if 0
   virtual void NotifyMediaStreamTrackEnded(dom::MediaStreamTrack* aTrack)
   {
     TrackID trackID = aTrack->GetTrackID();
     // We override this so we can also tell the backend to stop capturing if the track ends
     LOG(("track %d ending, type = %s",
@@ -799,24 +797,24 @@ public:
   virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) override
   {
     if (mSourceStream) {
       mSourceStream->RemoveDirectListener(aListener);
     }
   }
 
   // let us intervene for direct listeners when someone does track.enabled = false
-  virtual void SetTrackEnabled(TrackID aID, bool aEnabled) override
+  virtual void SetTrackEnabled(TrackID aTrackID, bool aEnabled) override
   {
     // We encapsulate the SourceMediaStream and TrackUnion into one entity, so
     // we can handle the disabling at the SourceMediaStream
 
-    // We need to find the input track ID for output ID aID, so we let the TrackUnion
+    // We need to find the input track ID for output ID aTrackID, so we let the TrackUnion
     // forward the request to the source and translate the ID
-    GetStream()->AsProcessedStream()->ForwardTrackEnabled(aID, aEnabled);
+    GetStream()->AsProcessedStream()->ForwardTrackEnabled(aTrackID, aEnabled);
   }
 
   virtual DOMLocalMediaStream* AsDOMLocalMediaStream() override
   {
     return this;
   }
 
   virtual MediaEngineSource* GetMediaEngine(TrackID aTrackID) override
@@ -3062,58 +3060,127 @@ GetUserMediaCallbackMediaStreamListener:
                                     dom::AudioChannel::Normal);
     graph->UnregisterCaptureStreamForWindow(mWindowID);
     mStream->Destroy();
   }
 }
 
 // ApplyConstraints for track
 
-void
+already_AddRefed<GetUserMediaCallbackMediaStreamListener::PledgeVoid>
 GetUserMediaCallbackMediaStreamListener::ApplyConstraintsToTrack(
-    TrackID aID,
+    nsPIDOMWindow* aWindow,
+    TrackID aTrackID,
     bool aIsAudio,
     const MediaTrackConstraints& aConstraints)
 {
-  if (((aIsAudio && mAudioDevice) ||
-       (!aIsAudio && mVideoDevice)) && !mStopped)
+  MOZ_ASSERT(NS_IsMainThread());
+  nsRefPtr<PledgeVoid> p = new PledgeVoid();
+
+  if (!(((aIsAudio && mAudioDevice) ||
+         (!aIsAudio && mVideoDevice)) && !mStopped))
   {
-    // XXX to support multiple tracks of a type in a stream, this should key off
-    // the TrackID and not just the type
-    MediaManager::PostTask(FROM_HERE,
-      new MediaOperationTask(MEDIA_APPLYCONSTRAINTS_TRACK,
-                             this, nullptr, nullptr,
-                             aIsAudio  ? mAudioDevice.get() : nullptr,
-                             !aIsAudio ? mVideoDevice.get() : nullptr,
-                             mFinished, mWindowID, nullptr, aConstraints));
-  } else {
     LOG(("gUM track %d applyConstraints, but we don't have type %s",
-         aID, aIsAudio ? "audio" : "video"));
+         aTrackID, aIsAudio ? "audio" : "video"));
+    p->Resolve(false);
+    return p.forget();
   }
+
+  // XXX to support multiple tracks of a type in a stream, this should key off
+  // the TrackID and not just the type
+  nsRefPtr<AudioDevice> audioDevice = aIsAudio ? mAudioDevice.get() : nullptr;
+  nsRefPtr<VideoDevice> videoDevice = !aIsAudio ? mVideoDevice.get() : nullptr;
+
+  nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
+  uint32_t id = mgr->mOutstandingVoidPledges.Append(*p);
+  uint64_t windowId = aWindow->WindowID();
+
+  MediaManager::PostTask(FROM_HERE, NewTaskFrom([id, windowId,
+                                                 audioDevice, videoDevice,
+                                                 aConstraints]() mutable {
+    MOZ_ASSERT(MediaManager::IsInMediaThread());
+    nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
+    const char* badConstraint = nullptr;
+    nsresult rv = NS_OK;
+
+    if (audioDevice) {
+      rv = audioDevice->Restart(aConstraints, mgr->mPrefs);
+      if (rv == NS_ERROR_NOT_AVAILABLE) {
+        nsTArray<nsRefPtr<AudioDevice>> audios;
+        audios.AppendElement(audioDevice);
+        badConstraint = MediaConstraintsHelper::SelectSettings(aConstraints,
+                                                               audios);
+      }
+    } else {
+      rv = videoDevice->Restart(aConstraints, mgr->mPrefs);
+      if (rv == NS_ERROR_NOT_AVAILABLE) {
+        nsTArray<nsRefPtr<VideoDevice>> videos;
+        videos.AppendElement(videoDevice);
+        badConstraint = MediaConstraintsHelper::SelectSettings(aConstraints,
+                                                               videos);
+      }
+    }
+    NS_DispatchToMainThread(do_AddRef(NewRunnableFrom([id, windowId, rv,
+                                                       badConstraint]() mutable {
+      MOZ_ASSERT(NS_IsMainThread());
+      nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
+      if (!mgr) {
+        return NS_OK;
+      }
+      nsRefPtr<PledgeVoid> p = mgr->mOutstandingVoidPledges.Remove(id);
+      if (p) {
+        if (NS_SUCCEEDED(rv)) {
+          p->Resolve(false);
+        } else {
+          nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
+              (nsGlobalWindow::GetInnerWindowWithId(windowId));
+          if (window) {
+            if (rv == NS_ERROR_NOT_AVAILABLE) {
+              nsString constraint;
+              constraint.AssignASCII(badConstraint);
+              nsRefPtr<MediaStreamError> error =
+                  new MediaStreamError(window,
+                                       NS_LITERAL_STRING("OverconstrainedError"),
+                                       NS_LITERAL_STRING(""),
+                                       constraint);
+              p->Reject(error);
+            } else {
+              nsRefPtr<MediaStreamError> error =
+                  new MediaStreamError(window,
+                                       NS_LITERAL_STRING("InternalError"));
+              p->Reject(error);
+            }
+          }
+        }
+      }
+      return NS_OK;
+    })));
+  }));
+  return p.forget();
 }
 
 // Stop backend for track
 
 void
-GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aID, bool aIsAudio)
+GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID, bool aIsAudio)
 {
   if (((aIsAudio && mAudioDevice) ||
        (!aIsAudio && mVideoDevice)) && !mStopped)
   {
     // XXX to support multiple tracks of a type in a stream, this should key off
     // the TrackID and not just the type
     MediaManager::PostTask(FROM_HERE,
       new MediaOperationTask(MEDIA_STOP_TRACK,
                              this, nullptr, nullptr,
                              aIsAudio  ? mAudioDevice.get() : nullptr,
                              !aIsAudio ? mVideoDevice.get() : nullptr,
                              mFinished, mWindowID, nullptr));
   } else {
     LOG(("gUM track %d ended, but we don't have type %s",
-         aID, aIsAudio ? "audio" : "video"));
+         aTrackID, aIsAudio ? "audio" : "video"));
   }
 }
 
 // Called from the MediaStreamGraph thread
 void
 GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph* aGraph)
 {
   mFinished = true;
@@ -3169,19 +3236,16 @@ GetUserMediaNotificationEvent::Run()
     msg = NS_LITERAL_STRING("shutdown");
     if (mListener) {
       mListener->SetStopped();
     }
     break;
   case STOPPED_TRACK:
     msg = NS_LITERAL_STRING("shutdown");
     break;
-  case APPLIED_CONSTRAINTS:
-    msg = NS_LITERAL_STRING("constraints-changed");
-    break;
   }
 
   nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
   return MediaManager::NotifyRecordingStatusChange(window, msg, mIsAudio, mIsVideo);
 }
 
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -162,18 +162,22 @@ public:
     }
     return mStream->AsSourceStream();
   }
 
   void StopSharing();
 
   void StopTrack(TrackID aID, bool aIsAudio);
 
-  void ApplyConstraintsToTrack(TrackID aID, bool aIsAudio,
-                               const dom::MediaTrackConstraints& aConstraints);
+  typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
+
+  already_AddRefed<PledgeVoid>
+  ApplyConstraintsToTrack(nsPIDOMWindow* aWindow,
+                          TrackID aID, bool aIsAudio,
+                          const dom::MediaTrackConstraints& aConstraints);
 
   // mVideo/AudioDevice are set by Activate(), so we assume they're capturing
   // if set and represent a real capture device.
   bool CapturingVideo()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     return mVideoDevice && !mStopped &&
            mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
@@ -318,17 +322,16 @@ private:
 
 class GetUserMediaNotificationEvent: public nsRunnable
 {
   public:
     enum GetUserMediaStatus {
       STARTING,
       STOPPING,
       STOPPED_TRACK,
-      APPLIED_CONSTRAINTS,
     };
     GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener,
                                   GetUserMediaStatus aStatus,
                                   bool aIsAudio, bool aIsVideo, uint64_t aWindowID)
     : mListener(aListener) , mStatus(aStatus) , mIsAudio(aIsAudio)
     , mIsVideo(aIsVideo), mWindowID(aWindowID) {}
 
     GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
@@ -357,17 +360,16 @@ class GetUserMediaNotificationEvent: pub
     nsRefPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
 };
 
 typedef enum {
   MEDIA_START,
   MEDIA_STOP,
   MEDIA_STOP_TRACK,
   MEDIA_DIRECT_LISTENERS,
-  MEDIA_APPLYCONSTRAINTS_TRACK,
 } MediaOperation;
 
 class MediaManager;
 class GetUserMediaTask;
 
 class ReleaseMediaOperationResource : public nsRunnable
 {
 public:
@@ -388,16 +390,17 @@ typedef nsClassHashtable<nsUint64HashKey
 typedef void (*WindowListenerCallback)(MediaManager *aThis,
                                        uint64_t aWindowID,
                                        StreamListeners *aListeners,
                                        void *aData);
 
 class MediaManager final : public nsIMediaManagerService,
                            public nsIObserver
 {
+  friend GetUserMediaCallbackMediaStreamListener;
 public:
   static already_AddRefed<MediaManager> GetInstance();
 
   // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
   // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
   // from MediaManager thread.
   static MediaManager* Get();
   static MediaManager* GetIfExists();
@@ -515,16 +518,17 @@ private:
 
   Mutex mMutex;
   // protected with mMutex:
   RefPtr<MediaEngine> mBackend;
 
   static StaticRefPtr<MediaManager> sSingleton;
 
   media::CoatCheck<PledgeSourceSet> mOutstandingPledges;
+  media::CoatCheck<GetUserMediaCallbackMediaStreamListener::PledgeVoid> mOutstandingVoidPledges;
 #if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK)
   nsRefPtr<nsDOMCameraManager> mCameraManager;
 #endif
 public:
   media::CoatCheck<media::Pledge<nsCString>> mGetOriginKeyPledges;
   ScopedDeletePtr<media::Parent<media::NonE10s>> mNonE10sParent;
 };
 
--- a/dom/media/tests/mochitest/test_getUserMedia_constraints.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_constraints.html
@@ -112,15 +112,20 @@ runTest(function() {
   return tests.reduce((p, test) =>
     p.then(() => navigator.mediaDevices.getUserMedia(test.constraints))
     .then(() => is(null, test.error, test.message), e => {
       is(e.name, test.error, test.message + ": " + e.message);
       if (test.constraint) {
         is(e.constraint, test.constraint,
            test.message + " w/correct constraint.");
       }
-    }), p);
+    }), p)
+    .then(() => navigator.mediaDevices.getUserMedia({video: true, audio: true}))
+    .then(stream => stream.getVideoTracks()[0].applyConstraints({ width: 320 })
+      .then(() => stream.getAudioTracks()[0].applyConstraints({ })))
+    .then(() => ok(true, "applyConstraints code exercised"))
+    // TODO: Test outcome once fake devices support constraints (Bug 1088621)
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -212,26 +212,33 @@ MediaEngineRemoteVideoSource::Stop(mozil
   return NS_OK;
 }
 
 nsresult
 MediaEngineRemoteVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
                                       const MediaEnginePrefs& aPrefs,
                                       const nsString& aDeviceId)
 {
-  if (mState == kStarted && mInitDone &&
-      ChooseCapability(aConstraints, aPrefs, aDeviceId)) {
-    mozilla::camera::StopCapture(mCapEngine, mCaptureIndex);
-    if (mozilla::camera::StartCapture(mCapEngine,
-                                      mCaptureIndex, mCapability, this)) {
-      LOG(("StartCapture failed"));
-      return NS_ERROR_FAILURE;
-    }
+  if (!mInitDone) {
+    LOG(("Init not done"));
+    return NS_ERROR_FAILURE;
+  }
+  if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  if (mState != kStarted) {
+    return NS_OK;
   }
 
+  mozilla::camera::StopCapture(mCapEngine, mCaptureIndex);
+  if (mozilla::camera::StartCapture(mCapEngine,
+                                    mCaptureIndex, mCapability, this)) {
+    LOG(("StartCapture failed"));
+    return NS_ERROR_FAILURE;
+  }
   return NS_OK;
 }
 
 void
 MediaEngineRemoteVideoSource::NotifyPull(MediaStreamGraph* aGraph,
                                          SourceMediaStream* aSource,
                                          TrackID aID, StreamTime aDesiredTime)
 {