Bug 1032839 - replaceTrack API. r=smaug, r=jesup
authorJan-Ivar Bruaroey <jib@mozilla.com>
Fri, 15 Aug 2014 01:33:09 -0400
changeset 201608 71c5e8aa91dd7885a42dc36f018e655bf60a6ab9
parent 201607 8b7c34d6dec5a22760138da5aefb3dba2a0a9241
child 201609 e09f215b65f86da6b3e440acf9e3ab9b370a770c
push id27375
push userryanvm@gmail.com
push dateTue, 26 Aug 2014 19:56:59 +0000
treeherdermozilla-central@f9bfe115fee5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, jesup
bugs1032839
milestone34.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 1032839 - replaceTrack API. r=smaug, r=jesup
dom/media/PeerConnection.js
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/test_peerConnection_replaceTrack.html
dom/webidl/PeerConnectionImpl.webidl
dom/webidl/PeerConnectionObserver.webidl
dom/webidl/RTCRtpSender.webidl
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
media/webrtc/signaling/test/FakePCObserver.h
media/webrtc/signaling/test/signaling_unittests.cpp
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -298,16 +298,19 @@ function RTCPeerConnection() {
   this._closed = false;
 
   this._onCreateOfferSuccess = null;
   this._onCreateOfferFailure = null;
   this._onCreateAnswerSuccess = null;
   this._onCreateAnswerFailure = null;
   this._onGetStatsSuccess = null;
   this._onGetStatsFailure = null;
+  this._onReplaceTrackSender = null;
+  this._onReplaceTrackSuccess = null;
+  this._onReplaceTrackFailure = null;
 
   this._pendingType = null;
   this._localType = null;
   this._remoteType = null;
   this._trickleIce = false;
   this._peerIdentity = null;
 
   /**
@@ -807,26 +810,45 @@ RTCPeerConnection.prototype = {
       throw new this._win.DOMError("", "invalid stream.");
     }
     if (stream.getTracks().indexOf(track) == -1) {
       throw new this._win.DOMError("", "track is not in stream.");
     }
     this._checkClosed();
     this._impl.addTrack(track, stream);
     let sender = this._win.RTCRtpSender._create(this._win,
-                                                new RTCRtpSender(this, track));
+                                                new RTCRtpSender(this, track,
+                                                                 stream));
     this._senders.push({ sender: sender, stream: stream });
     return sender;
   },
 
   removeTrack: function(sender) {
      // Bug 844295: Not implementing this functionality.
      throw new this._win.DOMError("", "removeTrack not yet implemented");
   },
 
+  _replaceTrack: function(sender, withTrack, onSuccess, onError) {
+    // TODO: Do a (sender._stream.getTracks().indexOf(track) == -1) check
+    //       on both track args someday.
+    //
+    // The proposed API will be that both tracks must already be in the same
+    // stream. However, since our MediaStreams currently are limited to one
+    // track per type, we allow replacement with an outside track not already
+    // in the same stream.
+    //
+    // Since a track may be replaced more than once, the track being replaced
+    // may not be in the stream either, so we check neither arg right now.
+
+    this._onReplaceTrackSender = sender;
+    this._onReplaceTrackSuccess = onSuccess;
+    this._onReplaceTrackFailure = onError;
+    this._impl.replaceTrack(sender.track, withTrack, sender._stream);
+  },
+
   close: function() {
     if (this._closed) {
       return;
     }
     this.changeIceConnectionState("closed");
     this._queueOrRun({ func: this._close, args: [false], wait: false });
     this._closed = true;
   },
@@ -1301,16 +1323,25 @@ PeerConnectionObserver.prototype = {
     this._dompc.dispatchEvent(ev);
   },
 
   onRemoveTrack: function(track, type) {
     this.dispatchEvent(new this._dompc._win.MediaStreamTrackEvent("removetrack",
                                                                   { track: track }));
   },
 
+  onReplaceTrackSuccess: function() {
+    this._dompc.callCB(this._dompc._onReplaceTrackSuccess);
+  },
+
+  onReplaceTrackError: function(code, message) {
+    this._dompc.callCB(this._dompc._onReplaceTrackError,
+                       new RTCError(code, message));
+  },
+
   foundIceCandidate: function(cand) {
     this.dispatchEvent(new this._dompc._win.RTCPeerConnectionIceEvent("icecandidate",
                                                                       { candidate: cand } ));
   },
 
   notifyDataChannel: function(channel) {
     this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel",
                                                                 { channel: channel }));
@@ -1332,25 +1363,35 @@ RTCPeerConnectionStatic.prototype = {
       .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
   },
 
   registerPeerConnectionLifecycleCallback: function(cb) {
     _globalPCList._registerPeerConnectionLifecycleCallback(this._winID, cb);
   },
 };
 
-function RTCRtpSender(pc, track) {
-  this.pc = pc;
+function RTCRtpSender(pc, track, stream) {
+  this._pc = pc;
   this.track = track;
+  this._stream = stream;
 }
 RTCRtpSender.prototype = {
   classDescription: "RTCRtpSender",
   classID: PC_SENDER_CID,
   contractID: PC_SENDER_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
+
+  replaceTrack: function(withTrack, onSuccess, onError) {
+    this._pc._checkClosed();
+    this._pc._queueOrRun({
+      func: this._pc._replaceTrack,
+      args: [this, withTrack, onSuccess, onError],
+      wait: false
+    });
+  }
 };
 
 function RTCRtpReceiver(pc, track) {
   this.pc = pc;
   this.track = track;
 }
 RTCRtpReceiver.prototype = {
   classDescription: "RTCRtpReceiver",
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -82,16 +82,18 @@ skip-if = buildapp == 'b2g' || os == 'an
 [test_peerConnection_close.html]
 [test_peerConnection_errorCallbacks.html]
 [test_peerConnection_offerRequiresReceiveAudio.html]
 skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_offerRequiresReceiveVideo.html]
 skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_offerRequiresReceiveVideoAudio.html]
 skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+[test_peerConnection_replaceTrack.html]
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_setLocalAnswerInHaveLocalOffer.html]
 [test_peerConnection_setLocalAnswerInStable.html]
 [test_peerConnection_setLocalOfferInHaveRemoteOffer.html]
 [test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html]
 [test_peerConnection_setRemoteAnswerInStable.html]
 [test_peerConnection_setRemoteOfferInHaveLocalOffer.html]
 [test_peerConnection_throwInCallbacks.html]
 skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+  <script type="application/javascript" src="pc.js"></script>
+  <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript;version=1.8">
+  createHTML({
+    bug: "1032839",
+    title: "Replace video track"
+  });
+
+  function isSenderOfTrack(sender) {
+    return sender.track == this;
+  }
+
+  // Test basically just verifies that success callback is called at this point
+
+  var test;
+  runNetworkTest(function () {
+    test = new PeerConnectionTest();
+    test.setMediaConstraints([{video: true}], [{video: true}]);
+    test.chain.removeAfter("PC_REMOTE_CHECK_MEDIA_FLOW_PRESENT");
+    test.chain.insertBefore("PC_LOCAL_CHECK_MEDIA_FLOW_PRESENT", [[
+      "PC_LOCAL_REPLACE_VIDEOTRACK",
+      function (test) {
+        var stream = test.pcLocal._pc.getLocalStreams()[0];
+        var track = stream.getVideoTracks()[0];
+        var sender = test.pcLocal._pc.getSenders().find(isSenderOfTrack, track);
+        ok(sender, "track has a sender");
+        navigator.mozGetUserMedia({video:true, fake: true}, function(newStream) {
+          sender.replaceTrack(newStream.getVideoTracks()[0],
+            function() {
+              ok(true, "replaceTrack success callback is called");
+              test.next();
+            },
+            function(err) {
+              ok(false, "replaceTrack failed with error = " + err);
+              test.next();
+            });
+        },
+        function(err) {
+          ok(false, "mozGetUserMedia failed. error = " + err);
+          test.next();
+        });
+      }
+    ]]);
+
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/PeerConnectionImpl.webidl
+++ b/dom/webidl/PeerConnectionImpl.webidl
@@ -39,16 +39,19 @@ interface PeerConnectionImpl  {
   void getStats(MediaStreamTrack? selector);
 
   /* Adds the tracks created by GetUserMedia */
   [Throws]
   void addTrack(MediaStreamTrack track, MediaStream... streams);
   [Throws]
   void removeTrack(MediaStreamTrack track);
   [Throws]
+  void replaceTrack(MediaStreamTrack thisTrack, MediaStreamTrack withTrack,
+                    MediaStream stream);
+  [Throws]
   void closeStreams();
 
   sequence<MediaStream> getLocalStreams();
   sequence<MediaStream> getRemoteStreams();
 
   /* As the ICE candidates roll in this one should be called each time
    * in order to keep the candidate list up-to-date for the next SDP-related
    * call PeerConnectionImpl does not parse ICE candidates, just sticks them
--- a/dom/webidl/PeerConnectionObserver.webidl
+++ b/dom/webidl/PeerConnectionObserver.webidl
@@ -23,16 +23,20 @@ interface PeerConnectionObserver
   void onAddIceCandidateSuccess();
   void onAddIceCandidateError(unsigned long name, DOMString message);
   void onIceCandidate(unsigned short level, DOMString mid, DOMString candidate);
 
   /* Stats callbacks */
   void onGetStatsSuccess(optional RTCStatsReportInternal report);
   void onGetStatsError(unsigned long name, DOMString message);
 
+  /* replaceTrack callbacks */
+  void onReplaceTrackSuccess();
+  void onReplaceTrackError(unsigned long name, DOMString message);
+
   /* Data channel callbacks */
   void notifyDataChannel(DataChannel channel);
 
   /* Notification of one of several types of state changed */
   void onStateChange(PCObserverStateType state);
 
   /* Changes to MediaStreamTracks */
   void onAddStream(MediaStream stream);
--- a/dom/webidl/RTCRtpSender.webidl
+++ b/dom/webidl/RTCRtpSender.webidl
@@ -6,9 +6,13 @@
  * The origin of this IDL file is
  * http://lists.w3.org/Archives/Public/public-webrtc/2014May/0067.html
  */
 
 [Pref="media.peerconnection.enabled",
  JSImplementation="@mozilla.org/dom/rtpsender;1"]
 interface RTCRtpSender {
   readonly attribute MediaStreamTrack track;
+
+  void replaceTrack(MediaStreamTrack track,
+                    VoidFunction successCallback,
+                    RTCPeerConnectionErrorCallback failureCallback);
 };
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1563,16 +1563,56 @@ PeerConnectionImpl::RemoveTrack(MediaStr
     mInternal->mCall->removeStream(stream_id, 1, VIDEO);
     MOZ_ASSERT(mNumVideoStreams > 0);
     mNumVideoStreams--;
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PeerConnectionImpl::ReplaceTrack(MediaStreamTrack& aThisTrack,
+                                 MediaStreamTrack& aWithTrack,
+                                 DOMMediaStream& aStream) {
+  PC_AUTO_ENTER_API_CALL(true);
+
+  // TODO: Do an aStream.HasTrack() check on both track args someday.
+  //
+  // The proposed API will be that both tracks must already be in the same
+  // stream. However, since our MediaStreams currently are limited to one
+  // track per type, we allow replacement with an outside track not already
+  // in the same stream. This works because sync happens receiver-side and
+  // timestamps are tied to capture.
+  //
+  // Since a track may be replaced more than once, the track being replaced
+  // may not be in the stream either, so we check neither arg right now.
+
+  // Insert magic here.
+  bool success = true;
+
+  nsRefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+  if (!pco) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  JSErrorResult rv;
+
+  if (success) {
+    pco->OnReplaceTrackSuccess(rv);
+  } else {
+    pco->OnReplaceTrackError(kInternalError,
+                             ObString("Failed to replace track"),
+                             rv);
+  }
+  if (rv.Failed()) {
+    CSFLogError(logTag, "Error firing replaceTrack callback");
+    return NS_ERROR_UNEXPECTED;
+  }
+  return NS_OK;
+}
+
 /*
 NS_IMETHODIMP
 PeerConnectionImpl::SetRemoteFingerprint(const char* hash, const char* fingerprint)
 {
   MOZ_ASSERT(hash);
   MOZ_ASSERT(fingerprint);
 
   if (fingerprint != nullptr && (strcmp(hash, "sha-1") == 0)) {
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -379,16 +379,24 @@ public:
                                mozilla::dom::MediaStreamTrack& aTrack)
   {
     rv = RemoveTrack(aTrack);
   }
 
   nsresult
   AddTrack(mozilla::dom::MediaStreamTrack& aTrack, DOMMediaStream& aStream);
 
+  NS_IMETHODIMP_TO_ERRORRESULT(ReplaceTrack, ErrorResult &rv,
+                               mozilla::dom::MediaStreamTrack& aThisTrack,
+                               mozilla::dom::MediaStreamTrack& aWithTrack,
+                               DOMMediaStream& aStream)
+  {
+    rv = ReplaceTrack(aThisTrack, aWithTrack, aStream);
+  }
+
   nsresult GetPeerIdentity(nsAString& peerIdentity)
   {
 #ifdef MOZILLA_INTERNAL_API
     if (mPeerIdentity) {
       peerIdentity = mPeerIdentity->ToString();
       return NS_OK;
     }
 #endif
--- a/media/webrtc/signaling/test/FakePCObserver.h
+++ b/media/webrtc/signaling/test/FakePCObserver.h
@@ -79,16 +79,18 @@ public:
   virtual NS_IMETHODIMP OnSetRemoteDescriptionError(uint32_t code, const char *msg, ER&) = 0;
   virtual NS_IMETHODIMP NotifyDataChannel(nsIDOMDataChannel *channel, ER&) = 0;
   virtual NS_IMETHODIMP OnStateChange(mozilla::dom::PCObserverStateType state_type, ER&,
                                       void* = nullptr) = 0;
   virtual NS_IMETHODIMP OnAddStream(nsIDOMMediaStream *stream, ER&) = 0;
   virtual NS_IMETHODIMP OnRemoveStream(ER&) = 0;
   virtual NS_IMETHODIMP OnAddTrack(ER&) = 0;
   virtual NS_IMETHODIMP OnRemoveTrack(ER&) = 0;
+  virtual NS_IMETHODIMP OnReplaceTrackSuccess(ER&) = 0;
+  virtual NS_IMETHODIMP OnReplaceTrackError(uint32_t code, const char *msg, ER&) = 0;
   virtual NS_IMETHODIMP OnAddIceCandidateSuccess(ER&) = 0;
   virtual NS_IMETHODIMP OnAddIceCandidateError(uint32_t code, const char *msg, ER&) = 0;
   virtual NS_IMETHODIMP OnIceCandidate(uint16_t level, const char *mid,
                                        const char *candidate, ER&) = 0;
 protected:
   sipcc::PeerConnectionImpl *pc;
   std::vector<mozilla::DOMMediaStream *> streams;
 };
--- a/media/webrtc/signaling/test/signaling_unittests.cpp
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -268,16 +268,18 @@ public:
   NS_IMETHODIMP OnSetLocalDescriptionError(uint32_t code, const char *msg, ER&);
   NS_IMETHODIMP OnSetRemoteDescriptionError(uint32_t code, const char *msg, ER&);
   NS_IMETHODIMP NotifyDataChannel(nsIDOMDataChannel *channel, ER&);
   NS_IMETHODIMP OnStateChange(PCObserverStateType state_type, ER&, void*);
   NS_IMETHODIMP OnAddStream(nsIDOMMediaStream *stream, ER&);
   NS_IMETHODIMP OnRemoveStream(ER&);
   NS_IMETHODIMP OnAddTrack(ER&);
   NS_IMETHODIMP OnRemoveTrack(ER&);
+  NS_IMETHODIMP OnReplaceTrackSuccess(ER&);
+  NS_IMETHODIMP OnReplaceTrackError(uint32_t code, const char *msg, ER&);
   NS_IMETHODIMP OnAddIceCandidateSuccess(ER&);
   NS_IMETHODIMP OnAddIceCandidateError(uint32_t code, const char *msg, ER&);
   NS_IMETHODIMP OnIceCandidate(uint16_t level, const char *mid, const char *cand, ER&);
 };
 
 NS_IMPL_ISUPPORTS(TestObserver, nsISupportsWeakReference)
 
 NS_IMETHODIMP
@@ -468,16 +470,28 @@ TestObserver::OnAddTrack(ER&)
 NS_IMETHODIMP
 TestObserver::OnRemoveTrack(ER&)
 {
   state = stateSuccess;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+TestObserver::OnReplaceTrackSuccess(ER&)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnReplaceTrackError(uint32_t code, const char *message, ER&)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 TestObserver::OnIceCandidate(uint16_t level,
                              const char * mid,
                              const char * candidate, ER&)
 {
   std::cout << name << ": onIceCandidate [" << level << "/"
             << mid << "] " << candidate << std::endl;
 
   // Check for duplicates.