Bug 1170958 - Refactor DOMMediaStream to contain a 3-stage track chain. r=roc
authorAndreas Pehrson <pehrsons@gmail.com>
Wed, 30 Sep 2015 09:31:54 +0800
changeset 265246 55e96325f4c107d21794c96aa82a1b7a3524e920
parent 265245 3e03ab746bc58a9fd268816cf1828b6926a8e017
child 265247 4b344f99a0f6fb49bad628626b28159d66a4a910
push id15444
push userkwierso@gmail.com
push dateWed, 30 Sep 2015 20:57:17 +0000
treeherderfx-team@b00623eb7735 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs1170958
milestone44.0a1
Bug 1170958 - Refactor DOMMediaStream to contain a 3-stage track chain. r=roc This lets us separate tracks by ownership like so: * Input - Owned by the producer of the DOMMediaStream (gUM etc.) * Owned - Contains Input tracks (per above) or tracks cloned tracks if this DOMMediaStream is a clone. * Playback - Contains Owned tracks plus tracks addTrack()ed to this DOMMediaStream minus tracks removeTrack()ed from this DOMMediaStream.
dom/camera/CameraPreviewMediaStream.h
dom/camera/DOMCameraControl.cpp
dom/camera/DOMCameraControl.h
dom/html/HTMLCanvasElement.cpp
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/CanvasCaptureMediaStream.cpp
dom/media/DOMMediaStream.cpp
dom/media/DOMMediaStream.h
dom/media/MediaManager.cpp
dom/media/MediaRecorder.cpp
dom/media/MediaStreamGraph.cpp
dom/media/MediaStreamGraph.h
dom/media/MediaStreamTrack.cpp
dom/media/MediaStreamTrack.h
dom/media/imagecapture/CaptureTask.cpp
dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
dom/media/webaudio/MediaStreamAudioSourceNode.cpp
dom/media/webspeech/recognition/SpeechRecognition.cpp
media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/test/FakeMediaStreams.h
--- a/dom/camera/CameraPreviewMediaStream.h
+++ b/dom/camera/CameraPreviewMediaStream.h
@@ -37,17 +37,16 @@ protected:
  */
 class CameraPreviewMediaStream : public MediaStream
 {
   typedef mozilla::layers::Image Image;
 
 public:
   explicit CameraPreviewMediaStream(DOMMediaStream* aWrapper);
 
-  virtual CameraPreviewMediaStream* AsCameraPreviewStream() override { return this; };
   virtual void AddAudioOutput(void* aKey) override;
   virtual void SetAudioOutputVolume(void* aKey, float aVolume) override;
   virtual void RemoveAudioOutput(void* aKey) override;
   virtual void AddVideoOutput(VideoFrameContainer* aContainer) override;
   virtual void RemoveVideoOutput(VideoFrameContainer* aContainer) override;
   virtual void Suspend() override {}
   virtual void Resume() override {}
   virtual void AddListener(MediaStreamListener* aListener) override;
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -274,18 +274,23 @@ nsDOMCameraControl::nsDOMCameraControl(u
     sCachedCameraControl = nullptr;
 #endif
     mCameraControl = ICameraControl::Create(aCameraId);
 #ifdef MOZ_WIDGET_GONK
   }
 #endif
   mCurrentConfiguration = initialConfig.forget();
 
-  // Attach our DOM-facing media stream to our viewfinder stream.
-  InitStreamCommon(mInput);
+  // Register the playback listener directly on the camera input stream.
+  // We want as low latency as possible for the camera, thus avoiding
+  // MediaStreamGraph altogether. Don't do the regular InitStreamCommon()
+  // to avoid initializing the Owned and Playback streams. This is OK since
+  // we are not user/DOM facing anyway.
+  CreateAndAddPlaybackStreamListener(mInput);
+
   MOZ_ASSERT(mWindow, "Shouldn't be created with a null window!");
   if (mWindow->GetExtantDoc()) {
     CombineWithPrincipal(mWindow->GetExtantDoc()->NodePrincipal());
   }
 
   // Register a listener for camera events.
   mListener = new DOMCameraControlListener(this, mInput);
   mCameraControl->AddListener(mListener);
@@ -319,16 +324,21 @@ nsDOMCameraControl::nsDOMCameraControl(u
   }
 }
 
 nsDOMCameraControl::~nsDOMCameraControl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   /*invoke DOMMediaStream destroy*/
   Destroy();
+
+  if (mInput) {
+    mInput->Destroy();
+    mInput = nullptr;
+  }
 }
 
 JSObject*
 nsDOMCameraControl::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return CameraControlBinding::Wrap(aCx, this, aGivenProto);
 }
 
@@ -454,16 +464,22 @@ nsDOMCameraControl::Get(uint32_t aKey, n
       v.mRight,
       v.mWeight
     );
   }
 
   return NS_OK;
 }
 
+MediaStream*
+nsDOMCameraControl::GetCameraStream() const
+{
+  return mInput;
+}
+
 #define THROW_IF_NO_CAMERACONTROL(...)                                          \
   do {                                                                          \
     if (!mCameraControl) {                                                      \
       DOM_CAMERA_LOGW("mCameraControl is null at %s:%d\n", __func__, __LINE__); \
       aRv = NS_ERROR_NOT_AVAILABLE;                                             \
       return __VA_ARGS__;                                                       \
     }                                                                           \
   } while (0)
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -66,16 +66,18 @@ public:
                      const dom::CameraConfiguration& aInitialConfig,
                      dom::Promise* aPromise,
                      nsPIDOMWindow* aWindow);
 
   void Shutdown();
 
   nsPIDOMWindow* GetParentObject() const { return mWindow; }
 
+  MediaStream* GetCameraStream() const override;
+
   // Attributes.
   void GetEffect(nsString& aEffect, ErrorResult& aRv);
   void SetEffect(const nsAString& aEffect, ErrorResult& aRv);
   void GetWhiteBalanceMode(nsString& aMode, ErrorResult& aRv);
   void SetWhiteBalanceMode(const nsAString& aMode, ErrorResult& aRv);
   void GetSceneMode(nsString& aMode, ErrorResult& aRv);
   void SetSceneMode(const nsAString& aMode, ErrorResult& aRv);
   void GetFlashMode(nsString& aMode, ErrorResult& aRv);
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -537,17 +537,17 @@ HTMLCanvasElement::CaptureStream(const O
 
   TrackID videoTrackId = 1;
   nsresult rv = stream->Init(aFrameRate, videoTrackId);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
-  stream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO);
+  stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO);
   RegisterFrameCaptureListener(stream->FrameCaptureListener());
   return stream.forget();
 }
 
 nsresult
 HTMLCanvasElement::ExtractData(nsAString& aType,
                                const nsAString& aOptions,
                                nsIInputStream** aStream)
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -556,17 +556,17 @@ HTMLMediaElement::GetMozMediaSourceObjec
 {
   nsRefPtr<MediaSource> source = mMediaSource;
   return source.forget();
 }
 
 already_AddRefed<DOMMediaStream>
 HTMLMediaElement::GetSrcObject() const
 {
-  NS_ASSERTION(!mSrcAttrStream || mSrcAttrStream->GetStream(),
+  NS_ASSERTION(!mSrcAttrStream || mSrcAttrStream->GetPlaybackStream(),
                "MediaStream should have been set up properly");
   nsRefPtr<DOMMediaStream> stream = mSrcAttrStream;
   return stream.forget();
 }
 
 void
 HTMLMediaElement::SetSrcObject(DOMMediaStream& aValue)
 {
@@ -580,17 +580,17 @@ HTMLMediaElement::SetSrcObject(DOMMediaS
   DoLoad();
 }
 
 // TODO: Remove prefixed versions soon (1183495)
 
 already_AddRefed<DOMMediaStream>
 HTMLMediaElement::GetMozSrcObject() const
 {
-  NS_ASSERTION(!mSrcAttrStream || mSrcAttrStream->GetStream(),
+  NS_ASSERTION(!mSrcAttrStream || mSrcAttrStream->GetPlaybackStream(),
                "MediaStream should have been set up properly");
   nsRefPtr<DOMMediaStream> stream = mSrcAttrStream;
   return stream.forget();
 }
 
 void
 HTMLMediaElement::SetMozSrcObject(DOMMediaStream& aValue)
 {
@@ -1871,27 +1871,27 @@ HTMLMediaElement::CaptureStreamInternal(
   out->mStream = DOMMediaStream::CreateTrackUnionStream(window, aGraph);
   nsRefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
   out->mStream->CombineWithPrincipal(principal);
   out->mStream->SetCORSMode(mCORSMode);
   out->mFinishWhenEnded = aFinishWhenEnded;
 
   mAudioCaptured = true;
   if (mDecoder) {
-    mDecoder->AddOutputStream(out->mStream->GetStream()->AsProcessedStream(),
+    mDecoder->AddOutputStream(out->mStream->GetInputStream()->AsProcessedStream(),
                               aFinishWhenEnded);
     if (mReadyState >= HAVE_METADATA) {
       // Expose the tracks to JS directly.
       if (HasAudio()) {
         TrackID audioTrackId = mMediaInfo.mAudio.mTrackId;
-        out->mStream->CreateDOMTrack(audioTrackId, MediaSegment::AUDIO);
+        out->mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO);
       }
       if (HasVideo()) {
         TrackID videoTrackId = mMediaInfo.mVideo.mTrackId;
-        out->mStream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO);
+        out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO);
       }
     }
   }
   nsRefPtr<DOMMediaStream> result = out->mStream;
   return result.forget();
 }
 
 already_AddRefed<DOMMediaStream>
@@ -2441,17 +2441,17 @@ bool HTMLMediaElement::ParseAttribute(in
       // We cannot change the AudioChannel of a decoder.
       if (mDecoder) {
         return true;
       }
 
       mAudioChannel = audioChannel;
 
       if (mSrcStream) {
-        nsRefPtr<MediaStream> stream = mSrcStream->GetStream();
+        nsRefPtr<MediaStream> stream = GetSrcMediaStream();
         if (stream) {
           stream->SetAudioChannelType(mAudioChannel);
         }
       }
 
       return true;
     }
   }
@@ -2856,17 +2856,17 @@ nsresult HTMLMediaElement::FinishDecoder
   if (NS_FAILED(rv)) {
     ShutdownDecoder();
     LOG(LogLevel::Debug, ("%p Failed to load for decoder %p", this, aDecoder));
     return rv;
   }
 
   for (uint32_t i = 0; i < mOutputStreams.Length(); ++i) {
     OutputMediaStream* ms = &mOutputStreams[i];
-    aDecoder->AddOutputStream(ms->mStream->GetStream()->AsProcessedStream(),
+    aDecoder->AddOutputStream(ms->mStream->GetInputStream()->AsProcessedStream(),
                               ms->mFinishWhenEnded);
   }
 
 #ifdef MOZ_EME
   if (mMediaKeys) {
     mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
   }
 #endif
@@ -3100,20 +3100,20 @@ private:
   HTMLMediaElement* mElement;
 };
 
 void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags)
 {
   if (!mSrcStream) {
     return;
   }
-  // We might be in cycle collection with mSrcStream->GetStream() already
+  // We might be in cycle collection with mSrcStream->GetPlaybackStream() already
   // returning null due to unlinking.
 
-  MediaStream* stream = mSrcStream->GetStream();
+  MediaStream* stream = GetSrcMediaStream();
   bool shouldPlay = !(aFlags & REMOVING_SRC_STREAM) && !mPaused &&
       !mPausedForInactiveDocumentOrChannel && stream;
   if (shouldPlay == mSrcStreamIsPlaying) {
     return;
   }
   mSrcStreamIsPlaying = shouldPlay;
 
   if (shouldPlay) {
@@ -3178,17 +3178,17 @@ void HTMLMediaElement::SetupSrcMediaStre
 
   mSrcStream = aStream;
 
   nsIDOMWindow* window = OwnerDoc()->GetInnerWindow();
   if (!window) {
     return;
   }
 
-  nsRefPtr<MediaStream> stream = mSrcStream->GetStream();
+  nsRefPtr<MediaStream> stream = GetSrcMediaStream();
   if (stream) {
     stream->SetAudioChannelType(mAudioChannel);
   }
 
   UpdateSrcMediaStreamPlaying();
 
   // Note: we must call DisconnectTrackListListeners(...)  before dropping
   // mSrcStream.
@@ -3277,21 +3277,21 @@ void HTMLMediaElement::MetadataLoaded(co
     mPendingEncryptedInitData.mInitDatas.Clear();
 #endif // MOZ_EME
   }
 
   // Expose the tracks to JS directly.
   for (OutputMediaStream& out : mOutputStreams) {
     if (aInfo->HasAudio()) {
       TrackID audioTrackId = aInfo->mAudio.mTrackId;
-      out.mStream->CreateDOMTrack(audioTrackId, MediaSegment::AUDIO);
+      out.mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO);
     }
     if (aInfo->HasVideo()) {
       TrackID videoTrackId = aInfo->mVideo.mTrackId;
-      out.mStream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO);
+      out.mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO);
     }
   }
 
   // If this element had a video track, but consists only of an audio track now,
   // delete the VideoFrameContainer. This happens when the src is changed to an
   // audio only file.
   // Else update its dimensions.
   if (!aInfo->HasVideo()) {
@@ -4784,31 +4784,31 @@ NS_IMETHODIMP HTMLMediaElement::WindowAu
       mAudioCapturedByWindow = true;
       nsCOMPtr<nsPIDOMWindow> window =
         do_QueryInterface(OwnerDoc()->GetParentObject());
       uint64_t id = window->WindowID();
       MediaStreamGraph* msg =
         MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
                                       AudioChannel::Normal);
 
-      if (mSrcStream) {
-        mCaptureStreamPort = msg->ConnectToCaptureStream(id, mSrcStream->GetStream());
+      if (GetSrcMediaStream()) {
+        mCaptureStreamPort = msg->ConnectToCaptureStream(id, GetSrcMediaStream());
       } else {
         nsRefPtr<DOMMediaStream> stream = CaptureStreamInternal(false, msg);
-        mCaptureStreamPort = msg->ConnectToCaptureStream(id, stream->GetStream());
+        mCaptureStreamPort = msg->ConnectToCaptureStream(id, stream->GetPlaybackStream());
       }
     } else {
       mAudioCapturedByWindow = false;
       if (mDecoder) {
         ProcessedMediaStream* ps =
           mCaptureStreamPort->GetSource()->AsProcessedStream();
         MOZ_ASSERT(ps);
 
         for (uint32_t i = 0; i < mOutputStreams.Length(); i++) {
-          if (mOutputStreams[i].mStream->GetStream() == ps) {
+          if (mOutputStreams[i].mStream->GetPlaybackStream() == ps) {
             mOutputStreams.RemoveElementAt(i);
             break;
           }
         }
 
         mDecoder->RemoveOutputStream(ps);
       }
       mCaptureStreamPort->Destroy();
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -344,22 +344,29 @@ public:
    * be fired if we've not fired a timeupdate event (for any reason) in the
    * last 250ms, as required by the spec when the current time is periodically
    * increasing during playback.
    */
   virtual void FireTimeUpdate(bool aPeriodic) final override;
 
   /**
    * This will return null if mSrcStream is null, or if mSrcStream is not
-   * null but its GetStream() returns null --- which can happen during
+   * null but its GetPlaybackStream() returns null --- which can happen during
    * cycle collection unlinking!
    */
   MediaStream* GetSrcMediaStream() const
   {
-    return mSrcStream ? mSrcStream->GetStream() : nullptr;
+    if (!mSrcStream) {
+      return nullptr;
+    }
+    if (mSrcStream->GetCameraStream()) {
+      // XXX Remove this check with CameraPreviewMediaStream per bug 1124630.
+      return mSrcStream->GetCameraStream();
+    }
+    return mSrcStream->GetPlaybackStream();
   }
 
   // WebIDL
 
   MediaError* GetError() const
   {
     return mError;
   }
@@ -1088,31 +1095,26 @@ protected:
 
   // If non-negative, the time we should return for currentTime while playing
   // mSrcStream.
   double mSrcStreamPausedCurrentTime;
 
   // Holds a reference to the stream connecting this stream to the capture sink.
   nsRefPtr<MediaInputPort> mCaptureStreamPort;
 
-  // Holds a reference to a stream with mSrcStream as input but intended for
-  // playback. Used so we don't block playback of other video elements
-  // playing the same mSrcStream.
-  nsRefPtr<DOMMediaStream> mPlaybackStream;
-
   // Holds references to the DOM wrappers for the MediaStreams that we're
   // writing to.
   struct OutputMediaStream {
     nsRefPtr<DOMMediaStream> mStream;
     bool mFinishWhenEnded;
   };
   nsTArray<OutputMediaStream> mOutputStreams;
 
-  // Holds a reference to the MediaStreamListener attached to mPlaybackStream
-  // (or mSrcStream if mPlaybackStream is null).
+  // Holds a reference to the MediaStreamListener attached to mSrcStream's
+  // playback stream.
   nsRefPtr<StreamListener> mMediaStreamListener;
   // Holds a reference to the size-getting MediaStreamListener attached to
   // mSrcStream.
   nsRefPtr<StreamSizeListener> mMediaStreamSizeListener;
 
   // Holds a reference to the MediaSource, if any, referenced by the src
   // attribute on the media element.
   nsRefPtr<MediaSource> mSrcMediaSource;
--- a/dom/media/CanvasCaptureMediaStream.cpp
+++ b/dom/media/CanvasCaptureMediaStream.cpp
@@ -236,24 +236,24 @@ CanvasCaptureMediaStream::RequestFrame()
 }
 
 nsresult
 CanvasCaptureMediaStream::Init(const dom::Optional<double>& aFPS,
                                const TrackID& aTrackId)
 {
   if (!aFPS.WasPassed()) {
     mOutputStreamDriver =
-      new AutoDriver(GetStream()->AsSourceStream(), aTrackId);
+      new AutoDriver(GetInputStream()->AsSourceStream(), aTrackId);
   } else if (aFPS.Value() < 0) {
     return NS_ERROR_ILLEGAL_VALUE;
   } else {
     // Cap frame rate to 60 FPS for sanity
     double fps = std::min(60.0, aFPS.Value());
     mOutputStreamDriver =
-      new TimerDriver(GetStream()->AsSourceStream(), fps, aTrackId);
+      new TimerDriver(GetInputStream()->AsSourceStream(), fps, aTrackId);
   }
   return NS_OK;
 }
 
 already_AddRefed<CanvasCaptureMediaStream>
 CanvasCaptureMediaStream::CreateSourceStream(nsIDOMWindow* aWindow,
                                              HTMLCanvasElement* aCanvas)
 {
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -22,29 +22,117 @@
 #include "Layers.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 
 const TrackID TRACK_VIDEO_PRIMARY = 1;
 
-class DOMMediaStream::StreamListener : public MediaStreamListener {
+/**
+ * TrackPort is a representation of a MediaStreamTrack-MediaInputPort pair
+ * that make up a link between the Owned stream and the Playback stream.
+ *
+ * Semantically, the track is the identifier/key and the port the value of this
+ * connection.
+ *
+ * The input port can be shared between several TrackPorts. This is the case
+ * for DOMMediaStream's mPlaybackPort which forwards all tracks in its
+ * mOwnedStream automatically.
+ *
+ * If the MediaStreamTrack is owned by another DOMMediaStream (called A) than
+ * the one owning the TrackPort (called B), the input port (locked to the
+ * MediaStreamTrack's TrackID) connects A's mOwnedStream to B's mPlaybackStream.
+ *
+ * A TrackPort may never leave the DOMMediaStream it was created in. Internal
+ * use only.
+ */
+class DOMMediaStream::TrackPort
+{
 public:
-  explicit StreamListener(DOMMediaStream* aStream)
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(TrackPort)
+  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(TrackPort)
+
+  enum class InputPortOwnership {
+    OWNED = 1,
+    EXTERNAL
+  };
+
+  TrackPort(MediaInputPort* aInputPort,
+            MediaStreamTrack* aTrack,
+            const InputPortOwnership aOwnership)
+    : mInputPort(aInputPort)
+    , mTrack(aTrack)
+    , mOwnership(aOwnership)
+  {
+    MOZ_ASSERT(mInputPort);
+    MOZ_ASSERT(mTrack);
+
+    MOZ_COUNT_CTOR(TrackPort);
+  }
+
+protected:
+  virtual ~TrackPort()
+  {
+    MOZ_COUNT_DTOR(TrackPort);
+
+    if (mOwnership == InputPortOwnership::OWNED && mInputPort) {
+      mInputPort->Destroy();
+      mInputPort = nullptr;
+    }
+  }
+
+public:
+  /**
+   * Returns the source stream of the input port.
+   */
+  MediaStream* GetSource() const { return mInputPort ? mInputPort->GetSource()
+                                                     : nullptr; }
+
+  /**
+   * Returns the track ID this track is locked to in the source stream of the
+   * input port.
+   */
+  TrackID GetSourceTrackId() const { return mInputPort ? mInputPort->GetSourceTrackId()
+                                                       : TRACK_INVALID; }
+
+  MediaInputPort* GetInputPort() const { return mInputPort; }
+  MediaStreamTrack* GetTrack() const { return mTrack; }
+
+private:
+  nsRefPtr<MediaInputPort> mInputPort;
+  nsRefPtr<MediaStreamTrack> mTrack;
+
+  // Defines if we've been given ownership of the input port or if it's owned
+  // externally. The owner is responsible for destroying the port.
+  const InputPortOwnership mOwnership;
+};
+
+NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackPort, mTrack)
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::TrackPort, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::TrackPort, Release)
+
+/**
+ * Listener registered on the Playback stream to detect when tracks end and when
+ * all new tracks this iteration have been created - for when several tracks are
+ * queued by the source and committed all at once.
+ */
+class DOMMediaStream::PlaybackStreamListener : public MediaStreamListener {
+public:
+  explicit PlaybackStreamListener(DOMMediaStream* aStream)
     : mStream(aStream)
   {}
 
   // Main thread only
   void Forget() { mStream = nullptr; }
   DOMMediaStream* GetStream() { return mStream; }
 
   class TrackChange : public nsRunnable {
   public:
-    TrackChange(StreamListener* aListener,
+    TrackChange(PlaybackStreamListener* aListener,
                 TrackID aID, StreamTime aTrackOffset,
                 uint32_t aEvents, MediaSegment::Type aType,
                 MediaStream* aInputStream, TrackID aInputTrackID)
       : mListener(aListener), mID(aID), mEvents(aEvents), mType(aType)
       , mInputStream(aInputStream), mInputTrackID(aInputTrackID)
     {
     }
 
@@ -52,72 +140,56 @@ public:
     {
       NS_ASSERTION(NS_IsMainThread(), "main thread only");
 
       DOMMediaStream* stream = mListener->GetStream();
       if (!stream) {
         return NS_OK;
       }
 
-      nsRefPtr<MediaStreamTrack> track;
-      if (mEvents & MediaStreamListener::TRACK_EVENT_CREATED) {
-        track = stream->BindDOMTrack(mID, mType);
-        if (!track) {
-          stream->CreateDOMTrack(mID, mType);
-          track = stream->BindDOMTrack(mID, mType);
-        }
-        stream->NotifyMediaStreamTrackCreated(track);
-      } else {
-        track = stream->GetDOMTrackFor(mID);
+      MOZ_ASSERT(mEvents & MediaStreamListener::TRACK_EVENT_ENDED);
+      nsRefPtr<MediaStreamTrack> track = stream->FindOwnedDOMTrack(mInputStream, mID);
+      if (track) {
+        track->NotifyEnded();
       }
-      if (mEvents & MediaStreamListener::TRACK_EVENT_ENDED) {
-        if (track) {
-          track->NotifyEnded();
-          stream->NotifyMediaStreamTrackEnded(track);
-        } else {
-          NS_ERROR("track ended but not found");
-        }
+
+      track = stream->FindPlaybackDOMTrack(mInputStream, mInputTrackID);
+      if (track) {
+        stream->NotifyMediaStreamTrackEnded(track);
       }
       return NS_OK;
     }
 
     StreamTime mEndTime;
-    nsRefPtr<StreamListener> mListener;
+    nsRefPtr<PlaybackStreamListener> mListener;
     TrackID mID;
     uint32_t mEvents;
     MediaSegment::Type mType;
     nsRefPtr<MediaStream> mInputStream;
     TrackID mInputTrackID;
   };
 
-  /**
-   * Notify that changes to one of the stream tracks have been queued.
-   * aTrackEvents can be any combination of TRACK_EVENT_CREATED and
-   * TRACK_EVENT_ENDED. aQueuedMedia is the data being added to the track
-   * at aTrackOffset (relative to the start of the stream).
-   * aQueuedMedia can be null if there is no output.
-   */
   virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                         StreamTime aTrackOffset,
                                         uint32_t aTrackEvents,
                                         const MediaSegment& aQueuedMedia,
                                         MediaStream* aInputStream,
                                         TrackID aInputTrackID) override
   {
-    if (aTrackEvents & (TRACK_EVENT_CREATED | TRACK_EVENT_ENDED)) {
+    if (aTrackEvents & TRACK_EVENT_ENDED) {
       nsRefPtr<TrackChange> runnable =
         new TrackChange(this, aID, aTrackOffset, aTrackEvents,
                         aQueuedMedia.GetType(), aInputStream, aInputTrackID);
       aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
     }
   }
 
   class TracksCreatedRunnable : public nsRunnable {
   public:
-    explicit TracksCreatedRunnable(StreamListener* aListener)
+    explicit TracksCreatedRunnable(PlaybackStreamListener* aListener)
       : mListener(aListener)
     {
     }
 
     NS_IMETHOD Run()
     {
       MOZ_ASSERT(NS_IsMainThread());
 
@@ -125,17 +197,17 @@ public:
       if (!stream) {
         return NS_OK;
       }
 
       stream->TracksCreated();
       return NS_OK;
     }
 
-    nsRefPtr<StreamListener> mListener;
+    nsRefPtr<PlaybackStreamListener> mListener;
   };
 
   virtual void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override
   {
     nsRefPtr<TracksCreatedRunnable> runnable = new TracksCreatedRunnable(this);
     aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
   }
 
@@ -145,23 +217,25 @@ private:
 };
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMMediaStream,
                                                 DOMEventTargetHelper)
   tmp->Destroy();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwnedTracks)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMMediaStream,
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwnedTracks)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(DOMMediaStream, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(DOMMediaStream, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream)
@@ -180,19 +254,20 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMAu
 
 NS_IMPL_ADDREF_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
 NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream)
 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
 
 DOMMediaStream::DOMMediaStream()
-  : mLogicalStreamStartTime(0),
-    mStream(nullptr), mTracksCreated(false),
-    mNotifiedOfMediaStreamGraphShutdown(false), mCORSMode(CORS_NONE)
+  : mLogicalStreamStartTime(0), mInputStream(nullptr), mOwnedStream(nullptr),
+    mPlaybackStream(nullptr), mOwnedPort(nullptr), mPlaybackPort(nullptr),
+    mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false),
+    mCORSMode(CORS_NONE)
 {
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
 
   if (NS_SUCCEEDED(rv) && uuidgen) {
     nsID uuid;
     memset(&uuid, 0, sizeof(uuid));
@@ -212,121 +287,155 @@ DOMMediaStream::~DOMMediaStream()
 
 void
 DOMMediaStream::Destroy()
 {
   if (mListener) {
     mListener->Forget();
     mListener = nullptr;
   }
-  if (mStream) {
-    mStream->Destroy();
-    mStream = nullptr;
+  if (mPlaybackPort) {
+    mPlaybackPort->Destroy();
+    mPlaybackPort = nullptr;
+  }
+  if (mOwnedPort) {
+    mOwnedPort->Destroy();
+    mOwnedPort = nullptr;
+  }
+  if (mPlaybackStream) {
+    mPlaybackStream->Destroy();
+    mPlaybackStream = nullptr;
+  }
+  if (mOwnedStream) {
+    mOwnedStream->Destroy();
+    mOwnedStream = nullptr;
+  }
+  if (mInputStream) {
+    mInputStream->Destroy();
+    mInputStream = nullptr;
   }
 }
 
 JSObject*
 DOMMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return dom::MediaStreamBinding::Wrap(aCx, this, aGivenProto);
 }
 
 double
 DOMMediaStream::CurrentTime()
 {
-  if (!mStream) {
+  if (!mPlaybackStream) {
     return 0.0;
   }
-  return mStream->
-    StreamTimeToSeconds(mStream->GetCurrentTime() - mLogicalStreamStartTime);
+  return mPlaybackStream->
+    StreamTimeToSeconds(mPlaybackStream->GetCurrentTime() - mLogicalStreamStartTime);
 }
 
 void
 DOMMediaStream::GetId(nsAString& aID) const
 {
   aID = mID;
 }
 
 void
 DOMMediaStream::GetAudioTracks(nsTArray<nsRefPtr<AudioStreamTrack> >& aTracks)
 {
-  for (uint32_t i = 0; i < mTracks.Length(); ++i) {
-    AudioStreamTrack* t = mTracks[i]->AsAudioStreamTrack();
+  for (const nsRefPtr<TrackPort>& info : mTracks) {
+    AudioStreamTrack* t = info->GetTrack()->AsAudioStreamTrack();
     if (t) {
       aTracks.AppendElement(t);
     }
   }
 }
 
 void
 DOMMediaStream::GetVideoTracks(nsTArray<nsRefPtr<VideoStreamTrack> >& aTracks)
 {
-  for (uint32_t i = 0; i < mTracks.Length(); ++i) {
-    VideoStreamTrack* t = mTracks[i]->AsVideoStreamTrack();
+  for (const nsRefPtr<TrackPort>& info : mTracks) {
+    VideoStreamTrack* t = info->GetTrack()->AsVideoStreamTrack();
     if (t) {
       aTracks.AppendElement(t);
     }
   }
 }
 
 void
 DOMMediaStream::GetTracks(nsTArray<nsRefPtr<MediaStreamTrack> >& aTracks)
 {
-  aTracks.AppendElements(mTracks);
+  for (const nsRefPtr<TrackPort>& info : mTracks) {
+    aTracks.AppendElement(info->GetTrack());
+  }
 }
 
 bool
 DOMMediaStream::HasTrack(const MediaStreamTrack& aTrack) const
 {
-  return mTracks.Contains(&aTrack);
+  return !!FindPlaybackDOMTrack(aTrack.GetStream()->GetOwnedStream(), aTrack.GetTrackID());
+}
+
+bool
+DOMMediaStream::OwnsTrack(const MediaStreamTrack& aTrack) const
+{
+  return (aTrack.GetStream() == this) && HasTrack(aTrack);
 }
 
 bool
 DOMMediaStream::IsFinished()
 {
-  return !mStream || mStream->IsFinished();
+  return !mPlaybackStream || mPlaybackStream->IsFinished();
 }
 
 void
 DOMMediaStream::InitSourceStream(nsIDOMWindow* aWindow,
                                  MediaStreamGraph* aGraph)
 {
   mWindow = aWindow;
-  InitStreamCommon(aGraph->CreateSourceStream(this));
+  InitStreamCommon(aGraph->CreateSourceStream(nullptr), aGraph);
 }
 
 void
 DOMMediaStream::InitTrackUnionStream(nsIDOMWindow* aWindow,
                                      MediaStreamGraph* aGraph)
 {
   mWindow = aWindow;
-
-  InitStreamCommon(aGraph->CreateTrackUnionStream(this));
+  InitStreamCommon(aGraph->CreateTrackUnionStream(nullptr), aGraph);
 }
 
 void
 DOMMediaStream::InitAudioCaptureStream(nsIDOMWindow* aWindow,
                                        MediaStreamGraph* aGraph)
 {
   mWindow = aWindow;
 
   const TrackID AUDIO_TRACK = 1;
 
-  InitStreamCommon(aGraph->CreateAudioCaptureStream(this, AUDIO_TRACK));
-  CreateDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO);
+  InitStreamCommon(aGraph->CreateAudioCaptureStream(this, AUDIO_TRACK), aGraph);
+  CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO);
 }
 
 void
-DOMMediaStream::InitStreamCommon(MediaStream* aStream)
+DOMMediaStream::InitStreamCommon(MediaStream* aStream,
+                                 MediaStreamGraph* aGraph)
 {
-  mStream = aStream;
+  mInputStream = aStream;
+
+  // We pass null as the wrapper since it is only used to signal finished
+  // streams. This is only needed for the playback stream.
+  mOwnedStream = aGraph->CreateTrackUnionStream(nullptr);
+  mOwnedStream->SetAutofinish(true);
+  mOwnedPort = mOwnedStream->AllocateInputPort(mInputStream);
+
+  mPlaybackStream = aGraph->CreateTrackUnionStream(this);
+  mPlaybackStream->SetAutofinish(true);
+  mPlaybackPort = mPlaybackStream->AllocateInputPort(mOwnedStream);
 
   // Setup track listener
-  mListener = new StreamListener(this);
-  aStream->AddListener(mListener);
+  mListener = new PlaybackStreamListener(this);
+  mPlaybackStream->AddListener(mListener);
 }
 
 already_AddRefed<DOMMediaStream>
 DOMMediaStream::CreateSourceStream(nsIDOMWindow* aWindow,
                                    MediaStreamGraph* aGraph)
 {
   nsRefPtr<DOMMediaStream> stream = new DOMMediaStream();
   stream->InitSourceStream(aWindow, aGraph);
@@ -349,26 +458,26 @@ DOMMediaStream::CreateAudioCaptureStream
   nsRefPtr<DOMMediaStream> stream = new DOMMediaStream();
   stream->InitAudioCaptureStream(aWindow, aGraph);
   return stream.forget();
 }
 
 void
 DOMMediaStream::SetTrackEnabled(TrackID aTrackID, bool aEnabled)
 {
-  if (mStream) {
-    mStream->SetTrackEnabled(aTrackID, aEnabled);
+  if (mOwnedStream) {
+    mOwnedStream->SetTrackEnabled(aTrackID, aEnabled);
   }
 }
 
 void
 DOMMediaStream::StopTrack(TrackID aTrackID)
 {
-  if (mStream && mStream->AsSourceStream()) {
-    mStream->AsSourceStream()->EndTrack(aTrackID);
+  if (mInputStream && mInputStream->AsSourceStream()) {
+    mInputStream->AsSourceStream()->EndTrack(aTrackID);
   }
 }
 
 already_AddRefed<Promise>
 DOMMediaStream::ApplyConstraintsToTrack(TrackID aTrackID,
                                         const MediaTrackConstraints& aConstraints,
                                         ErrorResult &aRv)
 {
@@ -424,75 +533,76 @@ DOMMediaStream::AddPrincipalChangeObserv
 
 bool
 DOMMediaStream::RemovePrincipalChangeObserver(PrincipalChangeObserver* aObserver)
 {
   return mPrincipalChangeObservers.RemoveElement(aObserver);
 }
 
 MediaStreamTrack*
-DOMMediaStream::CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
+DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
 {
+  MOZ_ASSERT(FindOwnedDOMTrack(GetOwnedStream(), aTrackID) == nullptr);
+
   MediaStreamTrack* track;
   switch (aType) {
   case MediaSegment::AUDIO:
     track = new AudioStreamTrack(this, aTrackID);
     break;
   case MediaSegment::VIDEO:
     track = new VideoStreamTrack(this, aTrackID);
     break;
   default:
     MOZ_CRASH("Unhandled track type");
   }
-  mTracks.AppendElement(track);
+
+  nsRefPtr<TrackPort> ownedTrackPort =
+    new TrackPort(mOwnedPort, track, TrackPort::InputPortOwnership::EXTERNAL);
+  mOwnedTracks.AppendElement(ownedTrackPort.forget());
 
+  nsRefPtr<TrackPort> playbackTrackPort =
+    new TrackPort(mPlaybackPort, track, TrackPort::InputPortOwnership::EXTERNAL);
+  mTracks.AppendElement(playbackTrackPort.forget());
+
+  NotifyMediaStreamTrackCreated(track);
   return track;
 }
 
 MediaStreamTrack*
-DOMMediaStream::BindDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
+DOMMediaStream::FindOwnedDOMTrack(MediaStream* aOwningStream, TrackID aTrackID) const
 {
-  MediaStreamTrack* track = nullptr;
-  bool bindSuccess = false;
-  switch (aType) {
-  case MediaSegment::AUDIO: {
-    for (size_t i = 0; i < mTracks.Length(); ++i) {
-      track = mTracks[i]->AsAudioStreamTrack();
-      if (track && track->GetTrackID() == aTrackID) {
-        bindSuccess = true;
-        break;
-      }
-    }
-    break;
+  if (aOwningStream != mOwnedStream) {
+    return nullptr;
   }
-  case MediaSegment::VIDEO: {
-    for (size_t i = 0; i < mTracks.Length(); ++i) {
-      track = mTracks[i]->AsVideoStreamTrack();
-      if (track && track->GetTrackID() == aTrackID) {
-        bindSuccess = true;
-        break;
-      }
+
+  for (const nsRefPtr<TrackPort>& info : mOwnedTracks) {
+    if (info->GetTrack()->GetTrackID() == aTrackID) {
+      return info->GetTrack();
     }
-    break;
   }
-  default:
-    MOZ_CRASH("Unhandled track type");
-  }
-  return bindSuccess ? track : nullptr;
+  return nullptr;
 }
 
 MediaStreamTrack*
-DOMMediaStream::GetDOMTrackFor(TrackID aTrackID)
+DOMMediaStream::FindPlaybackDOMTrack(MediaStream* aInputStream, TrackID aInputTrackID) const
 {
-  for (uint32_t i = 0; i < mTracks.Length(); ++i) {
-    MediaStreamTrack* t = mTracks[i];
-    // We may add streams to our track list that are actually owned by
-    // a different DOMMediaStream. Ignore those.
-    if (t->GetTrackID() == aTrackID && t->GetStream() == this) {
-      return t;
+  for (const nsRefPtr<TrackPort>& info : mTracks) {
+    if (info->GetInputPort() == mPlaybackPort &&
+        aInputStream == mOwnedStream &&
+        aInputTrackID == info->GetTrack()->GetTrackID()) {
+      // This track is in our owned and playback streams.
+      return info->GetTrack();
+    }
+    if (info->GetInputPort()->GetSource() == aInputStream &&
+        info->GetSourceTrackId() == aInputTrackID) {
+      // This track is owned externally but in our playback stream.
+      MOZ_ASSERT(aInputTrackID != TRACK_NONE);
+      MOZ_ASSERT(aInputTrackID != TRACK_INVALID);
+      MOZ_ASSERT(aInputTrackID != TRACK_ANY);
+      return info->GetTrack();
     }
   }
   return nullptr;
 }
 
 void
 DOMMediaStream::NotifyMediaStreamGraphShutdown()
 {
@@ -521,17 +631,16 @@ DOMMediaStream::OnTracksAvailable(OnTrac
   }
   mRunOnTracksAvailable.AppendElement(aRunnable);
   CheckTracksAvailable();
 }
 
 void
 DOMMediaStream::TracksCreated()
 {
-  MOZ_ASSERT(!mTracks.IsEmpty());
   mTracksCreated = true;
   CheckTracksAvailable();
 }
 
 void
 DOMMediaStream::CheckTracksAvailable()
 {
   if (!mTracksCreated) {
@@ -540,16 +649,24 @@ DOMMediaStream::CheckTracksAvailable()
   nsTArray<nsAutoPtr<OnTracksAvailableCallback> > callbacks;
   callbacks.SwapElements(mRunOnTracksAvailable);
 
   for (uint32_t i = 0; i < callbacks.Length(); ++i) {
     callbacks[i]->NotifyTracksAvailable(this);
   }
 }
 
+void
+DOMMediaStream::CreateAndAddPlaybackStreamListener(MediaStream* aStream)
+{
+  MOZ_ASSERT(GetCameraStream(), "I'm a hack. Only DOMCameraControl may use me.");
+  mListener = new PlaybackStreamListener(this);
+  aStream->AddListener(mListener);
+}
+
 already_AddRefed<AudioTrack>
 DOMMediaStream::CreateAudioTrack(AudioStreamTrack* aStreamTrack)
 {
   nsAutoString id;
   nsAutoString label;
   aStreamTrack->GetId(id);
   aStreamTrack->GetLabel(label);
 
@@ -575,21 +692,21 @@ DOMMediaStream::ConstructMediaTracks(Aud
                                      VideoTrackList* aVideoTrackList)
 {
   MediaTrackListListener audioListener(aAudioTrackList);
   mMediaTrackListListeners.AppendElement(audioListener);
   MediaTrackListListener videoListener(aVideoTrackList);
   mMediaTrackListListeners.AppendElement(videoListener);
 
   int firstEnabledVideo = -1;
-  for (uint32_t i = 0; i < mTracks.Length(); ++i) {
-    if (AudioStreamTrack* t = mTracks[i]->AsAudioStreamTrack()) {
+  for (const nsRefPtr<TrackPort>& info : mTracks) {
+    if (AudioStreamTrack* t = info->GetTrack()->AsAudioStreamTrack()) {
       nsRefPtr<AudioTrack> track = CreateAudioTrack(t);
       aAudioTrackList->AddTrack(track);
-    } else if (VideoStreamTrack* t = mTracks[i]->AsVideoStreamTrack()) {
+    } else if (VideoStreamTrack* t = info->GetTrack()->AsVideoStreamTrack()) {
       nsRefPtr<VideoTrack> track = CreateVideoTrack(t);
       aVideoTrackList->AddTrack(track);
       firstEnabledVideo = (t->Enabled() && firstEnabledVideo < 0)
                           ? (aVideoTrackList->Length() - 1)
                           : firstEnabledVideo;
     }
   }
 
@@ -640,33 +757,33 @@ DOMMediaStream::NotifyMediaStreamTrackEn
   aTrack->GetId(id);
   for (uint32_t i = 0; i < mMediaTrackListListeners.Length(); ++i) {
     mMediaTrackListListeners[i].NotifyMediaTrackEnded(id);
   }
 }
 
 DOMLocalMediaStream::~DOMLocalMediaStream()
 {
-  if (mStream) {
+  if (mInputStream) {
     // Make sure Listeners of this stream know it's going away
     Stop();
   }
 }
 
 JSObject*
 DOMLocalMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return dom::LocalMediaStreamBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 DOMLocalMediaStream::Stop()
 {
-  if (mStream && mStream->AsSourceStream()) {
-    mStream->AsSourceStream()->EndAllTrackAndFinish();
+  if (mInputStream && mInputStream->AsSourceStream()) {
+    mInputStream->AsSourceStream()->EndAllTrackAndFinish();
   }
 }
 
 already_AddRefed<DOMLocalMediaStream>
 DOMLocalMediaStream::CreateSourceStream(nsIDOMWindow* aWindow,
                                         MediaStreamGraph* aGraph)
 {
   nsRefPtr<DOMLocalMediaStream> stream = new DOMLocalMediaStream();
@@ -731,17 +848,17 @@ already_AddRefed<DOMHwMediaStream>
 DOMHwMediaStream::CreateHwStream(nsIDOMWindow* aWindow)
 {
   nsRefPtr<DOMHwMediaStream> stream = new DOMHwMediaStream();
 
   MediaStreamGraph* graph =
     MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER,
                                   AudioChannel::Normal);
   stream->InitSourceStream(aWindow, graph);
-  stream->Init(stream->GetStream());
+  stream->Init(stream->GetInputStream());
 
   return stream.forget();
 }
 
 void
 DOMHwMediaStream::Init(MediaStream* stream)
 {
   SourceMediaStream* srcStream = stream->AsSourceStream();
@@ -784,17 +901,17 @@ DOMHwMediaStream::SetImageSize(uint32_t 
 #ifdef MOZ_WIDGET_GONK
   OverlayImage::Data imgData;
 
   imgData.mOverlayId = mOverlayImage->GetOverlayId();
   imgData.mSize = IntSize(width, height);
   mOverlayImage->SetData(imgData);
 #endif
 
-  SourceMediaStream* srcStream = GetStream()->AsSourceStream();
+  SourceMediaStream* srcStream = GetInputStream()->AsSourceStream();
   StreamBuffer::Track* track = srcStream->FindTrack(TRACK_VIDEO_PRIMARY);
 
   if (!track || !track->GetSegment()) {
     return;
   }
 
 #ifdef MOZ_WIDGET_GONK
   // Clear the old segment.
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -30,17 +30,19 @@
 #endif
 
 namespace mozilla {
 
 class DOMHwMediaStream;
 class DOMLocalMediaStream;
 class MediaStream;
 class MediaEngineSource;
+class MediaInputPort;
 class MediaStreamGraph;
+class ProcessedMediaStream;
 
 namespace dom {
 class AudioNode;
 class HTMLCanvasElement;
 class MediaStreamTrack;
 class AudioStreamTrack;
 class VideoStreamTrack;
 class AudioTrack;
@@ -59,19 +61,124 @@ class OverlayImage;
 class MediaStreamDirectListener;
 
 #define NS_DOMMEDIASTREAM_IID \
 { 0x8cb65468, 0x66c0, 0x444e, \
   { 0x89, 0x9f, 0x89, 0x1d, 0x9e, 0xd2, 0xbe, 0x7c } }
 
 /**
  * DOM wrapper for MediaStreams.
+ *
+ * To account for track operations such as clone(), addTrack() and
+ * removeTrack(), a DOMMediaStream wraps three internal (and chained)
+ * MediaStreams:
+ *   1. mInputStream
+ *      - Controlled by the owner/source of the DOMMediaStream.
+ *        It's a stream of the type indicated by
+ *      - DOMMediaStream::CreateSourceStream/CreateTrackUnionStream. A source
+ *        typically creates its DOMMediaStream, creates the MediaStreamTracks
+ *        owned by said stream, then gets the internal input stream to which it
+ *        feeds data for the previously created tracks.
+ *      - When necessary it can create tracks on the internal stream only and
+ *        their corresponding MediaStreamTracks will be asynchronously created.
+ *   2. mOwnedStream
+ *      - A TrackUnionStream containing tracks owned by this stream.
+ *      - The internal model of a MediaStreamTrack consists of its owning
+ *        DOMMediaStream and the TrackID of the corresponding internal track in
+ *        the owning DOMMediaStream's mOwnedStream.
+ *      - The owned stream is different from the input stream since a cloned
+ *        DOMMediaStream is also the owner of its (cloned) MediaStreamTracks.
+ *      - Stopping an original track shall not stop its clone. This is
+ *        solved by stopping it at the owned stream, while the clone's owned
+ *        stream gets data directly from the original input stream.
+ *      - A DOMMediaStream (original or clone) gets all tracks dynamically
+ *        added by the source automatically forwarded by having a TRACK_ANY
+ *        MediaInputPort set up from the owning DOMMediaStream's input stream
+ *        to this DOMMediaStream's owned stream.
+ *   3. mPlaybackStream
+ *      - A TrackUnionStream containing the tracks corresponding to the
+ *        MediaStreamTracks currently in this DOMMediaStream (per getTracks()).
+ *      - Similarly as for mOwnedStream, there's a TRACK_ANY MediaInputPort set
+ *        up from the owned stream to the playback stream to allow tracks
+ *        dynamically added by the source to be automatically forwarded to any
+ *        audio or video sinks.
+ *      - MediaStreamTracks added by addTrack() are set up with a MediaInputPort
+ *        locked to their internal TrackID, from their owning DOMMediaStream's
+ *        owned stream to this playback stream.
+ *
+ *
+ * A graphical representation of how tracks are connected in various cases as
+ * follows:
+ *
+ *                     addTrack()ed case:
+ * DOMStream A
+ *           Input        Owned          Playback
+ *            t1 ---------> t1 ------------> t1     <- MediaStreamTrack X
+ *                                                     (pointing to t1 in A)
+ *                                 --------> t2     <- MediaStreamTrack Y
+ *                                /                    (pointing to t1 in B)
+ * DOMStream B                   /
+ *           Input        Owned /        Playback
+ *            t1 ---------> t1 ------------> t1     <- MediaStreamTrack Y
+ *                                                     (pointing to t1 in B)
+ *
+ *                     removeTrack()ed case:
+ * DOMStream A
+ *           Input        Owned          Playback
+ *            t1 ---------> t1                      <- No tracks
+ *
+ *
+ *                     clone()d case:
+ * DOMStream A
+ *           Input        Owned          Playback
+ *            t1 ---------> t1 ------------> t1     <- MediaStreamTrack X
+ *               \                                     (pointing to t1 in A)
+ *                -----
+ * DOMStream B         \
+ *           Input      \ Owned          Playback
+ *                       -> t1 ------------> t1     <- MediaStreamTrack Y
+ *                                                     (pointing to t1 in B)
+ *
+ *
+ *            addTrack()ed, removeTrack()ed and clone()d case:
+ *
+ *  Here we have done the following:
+ *    var A = someStreamWithTwoTracks;
+ *    var B = someStreamWithOneTrack;
+ *    var X = A.getTracks()[0];
+ *    var Y = A.getTracks()[1];
+ *    var Z = B.getTracks()[0];
+ *    A.addTrack(Z);
+ *    A.removeTrack(X);
+ *    B.removeTrack(Z);
+ *    var A' = A.clone();
+ *
+ * DOMStream A
+ *           Input        Owned          Playback
+ *            t1 ---------> t1                      <- MediaStreamTrack X (removed)
+ *                                                     (pointing to t1 in A)
+ *            t2 ---------> t2 ------------> t2     <- MediaStreamTrack Y
+ *             \                                       (pointing to t2 in A)
+ *              \                    ------> t3     <- MediaStreamTrack Z
+ *               \                  /                  (pointing to t1 in B)
+ * DOMStream B    \                /
+ *           Input \      Owned   /      Playback
+ *            t1 ---^-----> t1 ---                  <- MediaStreamTrack Z (removed)
+ *              \    \                                 (pointing to t1 in B)
+ *               \    \
+ * DOMStream A'   \    \
+ *           Input \    \ Owned          Playback
+ *                  \    -> t1 ------------> t1     <- MediaStreamTrack Y'
+ *                   \                                 (pointing to t1 in A')
+ *                    ----> t2 ------------> t2     <- MediaStreamTrack Z'
+ *                                                     (pointing to t2 in A')
  */
 class DOMMediaStream : public DOMEventTargetHelper
 {
+  class TrackPort;
   friend class DOMLocalMediaStream;
   typedef dom::MediaStreamTrack MediaStreamTrack;
   typedef dom::AudioStreamTrack AudioStreamTrack;
   typedef dom::VideoStreamTrack VideoStreamTrack;
   typedef dom::AudioTrack AudioTrack;
   typedef dom::VideoTrack VideoTrack;
   typedef dom::AudioTrackList AudioTrackList;
   typedef dom::VideoTrackList VideoTrackList;
@@ -98,19 +205,48 @@ public:
   // WebIDL
   double CurrentTime();
 
   void GetId(nsAString& aID) const;
 
   void GetAudioTracks(nsTArray<nsRefPtr<AudioStreamTrack> >& aTracks);
   void GetVideoTracks(nsTArray<nsRefPtr<VideoStreamTrack> >& aTracks);
   void GetTracks(nsTArray<nsRefPtr<MediaStreamTrack> >& aTracks);
+
+  // NON-WebIDL
+
+  /**
+   * Returns true if this DOMMediaStream has aTrack in its mPlaybackStream.
+   */
   bool HasTrack(const MediaStreamTrack& aTrack) const;
 
-  MediaStream* GetStream() const { return mStream; }
+  /**
+   * Returns true if this DOMMediaStream owns aTrack.
+   */
+  bool OwnsTrack(const MediaStreamTrack& aTrack) const;
+
+  /**
+   * Returns the corresponding MediaStreamTrack if it's in our mOwnedStream.
+   */
+  MediaStreamTrack* FindOwnedDOMTrack(MediaStream* aOwningStream, TrackID aTrackID) const;
+
+  /**
+   * Returns the corresponding MediaStreamTrack if it's in our mPlaybackStream.
+   */
+  MediaStreamTrack* FindPlaybackDOMTrack(MediaStream* aOwningStream, TrackID aTrackID) const;
+
+  MediaStream* GetInputStream() const { return mInputStream; }
+  ProcessedMediaStream* GetOwnedStream() const { return mOwnedStream; }
+  ProcessedMediaStream* GetPlaybackStream() const { return mPlaybackStream; }
+
+  /**
+   * Allows a video element to identify this stream as a camera stream, which
+   * needs special treatment.
+   */
+  virtual MediaStream* GetCameraStream() const { return nullptr; }
 
   /**
    * Overridden in DOMLocalMediaStreams to allow getUserMedia to pass
    * data directly to RTCPeerConnection without going through graph queuing.
    * Returns a bool to let us know if direct data will be delivered.
    */
   virtual bool AddDirectListener(MediaStreamDirectListener *aListener) { return false; }
   virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) {}
@@ -212,21 +348,23 @@ public:
   static already_AddRefed<DOMMediaStream> CreateAudioCaptureStream(
     nsIDOMWindow* aWindow, MediaStreamGraph* aGraph);
 
   void SetLogicalStreamStartTime(StreamTime aTime)
   {
     mLogicalStreamStartTime = aTime;
   }
 
-  // Notifications from StreamListener.
-  // BindDOMTrack should only be called when it's safe to run script.
-  MediaStreamTrack* BindDOMTrack(TrackID aTrackID, MediaSegment::Type aType);
-  MediaStreamTrack* CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType);
-  MediaStreamTrack* GetDOMTrackFor(TrackID aTrackID);
+  /**
+   * Called for each track in our owned stream to indicate to JS that we
+   * are carrying that track.
+   *
+   * Creates a MediaStreamTrack, adds it to mTracks and returns it.
+   */
+  MediaStreamTrack* CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType);
 
   class OnTracksAvailableCallback {
   public:
     virtual ~OnTracksAvailableCallback() {}
     virtual void NotifyTracksAvailable(DOMMediaStream* aStream) = 0;
   };
   // When the initial set of tracks has been added, run
   // aCallback->NotifyTracksAvailable.
@@ -266,51 +404,75 @@ public:
   virtual void NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack);
 
   virtual void NotifyMediaStreamTrackEnded(MediaStreamTrack* aTrack);
 
 protected:
   virtual ~DOMMediaStream();
 
   void Destroy();
-  void InitSourceStream(nsIDOMWindow* aWindow,
-                        MediaStreamGraph* aGraph);
-  void InitTrackUnionStream(nsIDOMWindow* aWindow,
-                            MediaStreamGraph* aGraph);
-  void InitAudioCaptureStream(nsIDOMWindow* aWindow,
-                              MediaStreamGraph* aGraph);
-  void InitStreamCommon(MediaStream* aStream);
+  void InitSourceStream(nsIDOMWindow* aWindow, MediaStreamGraph* aGraph);
+  void InitTrackUnionStream(nsIDOMWindow* aWindow, MediaStreamGraph* aGraph);
+  void InitAudioCaptureStream(nsIDOMWindow* aWindow, MediaStreamGraph* aGraph);
+  void InitStreamCommon(MediaStream* aStream, MediaStreamGraph* aGraph);
   already_AddRefed<AudioTrack> CreateAudioTrack(AudioStreamTrack* aStreamTrack);
   already_AddRefed<VideoTrack> CreateVideoTrack(VideoStreamTrack* aStreamTrack);
 
   // Called when MediaStreamGraph has finished an iteration where tracks were
   // created.
   void TracksCreated();
 
   void CheckTracksAvailable();
 
-  class StreamListener;
-  friend class StreamListener;
+  class PlaybackStreamListener;
+  friend class PlaybackStreamListener;
+
+  // XXX Bug 1124630. Remove with CameraPreviewMediaStream.
+  void CreateAndAddPlaybackStreamListener(MediaStream*);
 
   // StreamTime at which the currentTime attribute would return 0.
   StreamTime mLogicalStreamStartTime;
 
   // We need this to track our parent object.
   nsCOMPtr<nsIDOMWindow> mWindow;
 
-  // MediaStream is owned by the graph, but we tell it when to die, and it won't
-  // die until we let it.
-  MediaStream* mStream;
+  // MediaStreams are owned by the graph, but we tell them when to die,
+  // and they won't die until we let them.
+
+  // This stream contains tracks used as input by us. Cloning happens from this
+  // stream. Tracks may exist in these stream but not in |mOwnedStream| if they
+  // have been stopped.
+  MediaStream* mInputStream;
+
+  // This stream contains tracks owned by us (if we were created directly from
+  // source, or cloned from some other stream). Tracks map to |mOwnedTracks|.
+  ProcessedMediaStream* mOwnedStream;
 
-  nsAutoTArray<nsRefPtr<MediaStreamTrack>,2> mTracks;
-  nsRefPtr<StreamListener> mListener;
+  // This stream contains tracks currently played by us, despite of owner.
+  // Tracks map to |mTracks|.
+  ProcessedMediaStream* mPlaybackStream;
+
+  // This port connects mInputStream to mOwnedStream. All tracks forwarded.
+  nsRefPtr<MediaInputPort> mOwnedPort;
+
+  // This port connects mOwnedStream to mPlaybackStream. All tracks not
+  // explicitly blocked due to removal are forwarded.
+  nsRefPtr<MediaInputPort> mPlaybackPort;
+
+  // MediaStreamTracks corresponding to tracks in our mOwnedStream.
+  nsAutoTArray<nsRefPtr<TrackPort>, 2> mOwnedTracks;
+
+  // MediaStreamTracks corresponding to tracks in our mPlaybackStream.
+  nsAutoTArray<nsRefPtr<TrackPort>, 2> mTracks;
+
+  nsRefPtr<PlaybackStreamListener> mListener;
 
   nsTArray<nsAutoPtr<OnTracksAvailableCallback> > mRunOnTracksAvailable;
 
-  // Set to true after MediaStreamGraph has created tracks for mStream.
+  // Set to true after MediaStreamGraph has created tracks for mPlaybackStream.
   bool mTracksCreated;
 
   nsString mID;
 
   // Keep these alive until the stream finishes
   nsTArray<nsCOMPtr<nsISupports> > mConsumersToKeepAlive;
 
   bool mNotifiedOfMediaStreamGraphShutdown;
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -696,19 +696,19 @@ public:
   // XXX This will not handle more complex cases well.
   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());
+      nsRefPtr<dom::MediaStreamTrack> ownedTrack = FindOwnedDOMTrack(mOwnedStream, aTrackID);
+      if (ownedTrack) {
+        mListener->StopTrack(aTrackID, !!ownedTrack->AsAudioStreamTrack());
       } else {
         LOG(("StopTrack(%d) on non-existent track", aTrackID));
       }
     }
   }
 
   virtual already_AddRefed<Promise>
   ApplyConstraintsToTrack(TrackID aTrackID,
@@ -729,17 +729,17 @@ public:
     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);
+    nsRefPtr<dom::MediaStreamTrack> track = FindOwnedDOMTrack(mOwnedStream, 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();
     }
@@ -806,17 +806,17 @@ public:
   // let us intervene for direct listeners when someone does track.enabled = false
   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 aTrackID, so we let the TrackUnion
     // forward the request to the source and translate the ID
-    GetStream()->AsProcessedStream()->ForwardTrackEnabled(aTrackID, aEnabled);
+    GetInputStream()->AsProcessedStream()->ForwardTrackEnabled(aTrackID, aEnabled);
   }
 
   virtual DOMLocalMediaStream* AsDOMLocalMediaStream() override
   {
     return this;
   }
 
   virtual MediaEngineSource* GetMediaEngine(TrackID aTrackID) override
@@ -922,17 +922,17 @@ public:
     {
       // We're in the main thread, so no worries here.
       if (!(mManager->IsWindowStillActive(mWindowID))) {
         return;
       }
 
       // Start currentTime from the point where this stream was successfully
       // returned.
-      aStream->SetLogicalStreamStartTime(aStream->GetStream()->GetCurrentTime());
+      aStream->SetLogicalStreamStartTime(aStream->GetPlaybackStream()->GetCurrentTime());
 
       // 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()"));
       mOnSuccess->OnSuccess(aStream);
     }
     uint64_t mWindowID;
     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
@@ -1008,36 +1008,36 @@ public:
     // them down instead.
     if (mAudioDevice &&
         mAudioDevice->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
       domStream = DOMLocalMediaStream::CreateAudioCaptureStream(window, msg);
       // It should be possible to pipe the capture stream to anything. CORS is
       // not a problem here, we got explicit user content.
       domStream->SetPrincipal(window->GetExtantDoc()->NodePrincipal());
       msg->RegisterCaptureStreamForWindow(
-            mWindowID, domStream->GetStream()->AsProcessedStream());
+            mWindowID, domStream->GetInputStream()->AsProcessedStream());
       window->SetAudioCapture(true);
     } else {
       // Normal case, connect the source stream to the track union stream to
       // avoid us blocking
       nsRefPtr<nsDOMUserMediaStream> trackunion =
         nsDOMUserMediaStream::CreateTrackUnionStream(window, mListener,
                                                      mAudioDevice, mVideoDevice,
                                                      msg);
-      trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true);
-      nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()->
+      trackunion->GetInputStream()->AsProcessedStream()->SetAutofinish(true);
+      nsRefPtr<MediaInputPort> port = trackunion->GetInputStream()->AsProcessedStream()->
         AllocateInputPort(stream);
       trackunion->mSourceStream = stream;
       trackunion->mPort = port.forget();
       // Log the relationship between SourceMediaStream and TrackUnion stream
       // Make sure logger starts before capture
       AsyncLatencyLogger::Get(true);
       LogLatency(AsyncLatencyLogger::MediaStreamCreate,
-          reinterpret_cast<uint64_t>(stream.get()),
-          reinterpret_cast<int64_t>(trackunion->GetStream()));
+                 reinterpret_cast<uint64_t>(stream.get()),
+                 reinterpret_cast<int64_t>(trackunion->GetInputStream()));
 
       nsCOMPtr<nsIPrincipal> principal;
       if (mPeerIdentity) {
         principal = nsNullPrincipal::Create();
         trackunion->SetPeerIdentity(mPeerIdentity.forget());
       } else {
         principal = window->GetExtantDoc()->NodePrincipal();
       }
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -1168,17 +1168,17 @@ MediaRecorder::NotifyOwnerDocumentActivi
     Stop(result);
   }
 }
 
 MediaStream*
 MediaRecorder::GetSourceMediaStream()
 {
   if (mDOMStream != nullptr) {
-    return mDOMStream->GetStream();
+    return mDOMStream->GetPlaybackStream();
   }
   MOZ_ASSERT(mAudioNode != nullptr);
   return mPipeStream ? mPipeStream.get() : mAudioNode->GetStream();
 }
 
 nsIPrincipal*
 MediaRecorder::GetSourcePrincipal()
 {
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -1596,17 +1596,17 @@ MediaStream::MediaStream(DOMMediaStream*
   , mMainThreadDestroyed(false)
   , mGraph(nullptr)
   , mAudioChannelType(dom::AudioChannel::Normal)
 {
   MOZ_COUNT_CTOR(MediaStream);
   // aWrapper should not already be connected to a MediaStream! It needs
   // to be hooked up to this stream, and since this stream is only just
   // being created now, aWrapper must not be connected to anything.
-  NS_ASSERTION(!aWrapper || !aWrapper->GetStream(),
+  NS_ASSERTION(!aWrapper || !aWrapper->GetPlaybackStream(),
                "Wrapper already has another media stream hooked up to it!");
 }
 
 size_t
 MediaStream::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t amount = 0;
 
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -422,17 +422,16 @@ public:
 
   friend class MediaStreamGraphImpl;
   friend class MediaInputPort;
   friend class AudioNodeExternalInputStream;
 
   virtual SourceMediaStream* AsSourceStream() { return nullptr; }
   virtual ProcessedMediaStream* AsProcessedStream() { return nullptr; }
   virtual AudioNodeStream* AsAudioNodeStream() { return nullptr; }
-  virtual CameraPreviewMediaStream* AsCameraPreviewStream() { return nullptr; }
 
   // These Impl methods perform the core functionality of the control methods
   // above, on the media graph thread.
   /**
    * Stop all stream activity and disconnect it from all inputs and outputs.
    * This must be idempotent.
    */
   virtual void DestroyImpl();
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -8,17 +8,17 @@
 #include "DOMMediaStream.h"
 #include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID)
-  : mStream(aStream), mTrackID(aTrackID), mEnded(false), mEnabled(true)
+  : mOwningStream(aStream), mTrackID(aTrackID), mEnded(false), mEnabled(true)
 {
 
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
 
   nsID uuid;
   memset(&uuid, 0, sizeof(uuid));
@@ -31,43 +31,43 @@ MediaStreamTrack::MediaStreamTrack(DOMMe
   mID = NS_ConvertASCIItoUTF16(chars);
 }
 
 MediaStreamTrack::~MediaStreamTrack()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaStreamTrack, DOMEventTargetHelper,
-                                   mStream)
+                                   mOwningStream)
 
 NS_IMPL_ADDREF_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaStreamTrack)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 void
 MediaStreamTrack::GetId(nsAString& aID) const
 {
   aID = mID;
 }
 
 void
 MediaStreamTrack::SetEnabled(bool aEnabled)
 {
   mEnabled = aEnabled;
-  mStream->SetTrackEnabled(mTrackID, aEnabled);
+  mOwningStream->SetTrackEnabled(mTrackID, aEnabled);
 }
 
 void
 MediaStreamTrack::Stop()
 {
-  mStream->StopTrack(mTrackID);
+  mOwningStream->StopTrack(mTrackID);
 }
 
 already_AddRefed<Promise>
 MediaStreamTrack::ApplyConstraints(const MediaTrackConstraints& aConstraints,
                                    ErrorResult &aRv)
 {
-  return mStream->ApplyConstraintsToTrack(mTrackID, aConstraints, aRv);
+  return GetStream()->ApplyConstraintsToTrack(mTrackID, aConstraints, aRv);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -30,20 +30,28 @@ public:
    * MediaStream owned by aStream.
    */
   MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrack,
                                            DOMEventTargetHelper)
 
-  DOMMediaStream* GetParentObject() const { return mStream; }
+  DOMMediaStream* GetParentObject() const { return mOwningStream; }
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override = 0;
 
-  DOMMediaStream* GetStream() const { return mStream; }
+  /**
+   * Returns the DOMMediaStream owning this track.
+   */
+  DOMMediaStream* GetStream() const { return mOwningStream; }
+
+  /**
+   * Returns the TrackID this stream has in its owning DOMMediaStream's Owned
+   * stream.
+   */
   TrackID GetTrackID() const { return mTrackID; }
   virtual AudioStreamTrack* AsAudioStreamTrack() { return nullptr; }
   virtual VideoStreamTrack* AsVideoStreamTrack() { return nullptr; }
 
   // WebIDL
   virtual void GetKind(nsAString& aKind) = 0;
   void GetId(nsAString& aID) const;
   void GetLabel(nsAString& aLabel) { aLabel.Truncate(); }
@@ -58,17 +66,17 @@ public:
 
   // Webrtc allows the remote side to name tracks whatever it wants, and we
   // need to surface this to content.
   void AssignId(const nsAString& aID) { mID = aID; }
 
 protected:
   virtual ~MediaStreamTrack();
 
-  nsRefPtr<DOMMediaStream> mStream;
+  nsRefPtr<DOMMediaStream> mOwningStream;
   TrackID mTrackID;
   nsString mID;
   bool mEnded;
   bool mEnabled;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/imagecapture/CaptureTask.cpp
+++ b/dom/media/imagecapture/CaptureTask.cpp
@@ -52,31 +52,31 @@ CaptureTask::AttachStream()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<dom::VideoStreamTrack> track = mImageCapture->GetVideoStreamTrack();
 
   nsRefPtr<DOMMediaStream> domStream = track->GetStream();
   domStream->AddPrincipalChangeObserver(this);
 
-  nsRefPtr<MediaStream> stream = domStream->GetStream();
+  nsRefPtr<MediaStream> stream = domStream->GetPlaybackStream();
   stream->AddListener(this);
 }
 
 void
 CaptureTask::DetachStream()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<dom::VideoStreamTrack> track = mImageCapture->GetVideoStreamTrack();
 
   nsRefPtr<DOMMediaStream> domStream = track->GetStream();
   domStream->RemovePrincipalChangeObserver(this);
 
-  nsRefPtr<MediaStream> stream = domStream->GetStream();
+  nsRefPtr<MediaStream> stream = domStream->GetPlaybackStream();
   stream->RemoveListener(this);
 }
 
 void
 CaptureTask::PrincipalChanged(DOMMediaStream* aMediaStream)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mPrincipalChanged = true;
--- a/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
@@ -29,19 +29,19 @@ MediaStreamAudioDestinationNode::MediaSt
               ChannelCountMode::Explicit,
               ChannelInterpretation::Speakers)
   , mDOMStream(
       DOMAudioNodeMediaStream::CreateTrackUnionStream(GetOwner(),
                                                       this,
                                                       aContext->Graph()))
 {
   // Ensure an audio track with the correct ID is exposed to JS
-  mDOMStream->CreateDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO);
+  mDOMStream->CreateOwnDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO);
 
-  ProcessedMediaStream* outputStream = mDOMStream->GetStream()->AsProcessedStream();
+  ProcessedMediaStream* outputStream = mDOMStream->GetInputStream()->AsProcessedStream();
   MOZ_ASSERT(!!outputStream);
   AudioNodeEngine* engine = new AudioNodeEngine(this);
   mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::EXTERNAL_OUTPUT);
   mPort = outputStream->AllocateInputPort(mStream, AudioNodeStream::AUDIO_TRACK);
 
   nsIDocument* doc = aContext->GetParentObject()->GetExtantDoc();
   if (doc) {
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
@@ -37,17 +37,17 @@ MediaStreamAudioSourceNode::MediaStreamA
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers),
     mInputStream(aMediaStream)
 {
   AudioNodeEngine* engine = new MediaStreamAudioSourceNodeEngine(this);
   mStream = AudioNodeExternalInputStream::Create(aContext->Graph(), engine);
   ProcessedMediaStream* outputStream = static_cast<ProcessedMediaStream*>(mStream.get());
-  mInputPort = outputStream->AllocateInputPort(aMediaStream->GetStream());
+  mInputPort = outputStream->AllocateInputPort(aMediaStream->GetPlaybackStream());
   mInputStream->AddConsumerToKeepAlive(static_cast<nsIDOMEventTarget*>(this));
 
   PrincipalChanged(mInputStream); // trigger enabling/disabling of the connector
   mInputStream->AddPrincipalChangeObserver(this);
 }
 
 MediaStreamAudioSourceNode::~MediaStreamAudioSourceNode()
 {
--- a/dom/media/webspeech/recognition/SpeechRecognition.cpp
+++ b/dom/media/webspeech/recognition/SpeechRecognition.cpp
@@ -554,35 +554,35 @@ SpeechRecognition::NotifyError(SpeechEve
  **************************************/
 NS_IMETHODIMP
 SpeechRecognition::StartRecording(DOMMediaStream* aDOMStream)
 {
   // hold a reference so that the underlying stream
   // doesn't get Destroy()'ed
   mDOMStream = aDOMStream;
 
-  if (NS_WARN_IF(!mDOMStream->GetStream())) {
+  if (NS_WARN_IF(!mDOMStream->GetPlaybackStream())) {
     return NS_ERROR_UNEXPECTED;
   }
   mSpeechListener = new SpeechStreamListener(this);
-  mDOMStream->GetStream()->AddListener(mSpeechListener);
+  mDOMStream->GetPlaybackStream()->AddListener(mSpeechListener);
 
   mEndpointer.StartSession();
 
   return mSpeechDetectionTimer->Init(this, kSPEECH_DETECTION_TIMEOUT_MS,
                                      nsITimer::TYPE_ONE_SHOT);
 }
 
 NS_IMETHODIMP
 SpeechRecognition::StopRecording()
 {
   // we only really need to remove the listener explicitly when testing,
   // as our JS code still holds a reference to mDOMStream and only assigning
   // it to nullptr isn't guaranteed to free the stream and the listener.
-  mDOMStream->GetStream()->RemoveListener(mSpeechListener);
+  mDOMStream->GetPlaybackStream()->RemoveListener(mSpeechListener);
   mSpeechListener = nullptr;
   mDOMStream = nullptr;
 
   mEndpointer.EndSession();
   DispatchTrustedEvent(NS_LITERAL_STRING("audioend"));
 
   return NS_OK;
 }
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -678,25 +678,25 @@ nsresult MediaPipelineTransmit::Transpor
 
   return NS_OK;
 }
 
 nsresult MediaPipelineTransmit::ReplaceTrack(DOMMediaStream *domstream,
                                              const std::string& track_id) {
   // MainThread, checked in calls we make
   MOZ_MTLOG(ML_DEBUG, "Reattaching pipeline " << description_ << " to stream "
-            << static_cast<void *>(domstream->GetStream())
+            << static_cast<void *>(domstream->GetOwnedStream())
             << " track " << track_id << " conduit type=" <<
             (conduit_->type() == MediaSessionConduit::AUDIO ?"audio":"video"));
 
   if (domstream_) { // may be excessive paranoia
     DetachMediaStream();
   }
   domstream_ = domstream; // Detach clears it
-  stream_ = domstream->GetStream();
+  stream_ = domstream->GetOwnedStream();
   // Unsets the track id after RemoveListener() takes effect.
   listener_->UnsetTrackId(stream_->GraphImpl());
   track_id_ = track_id;
   AttachToTrack(track_id);
   return NS_OK;
 }
 
 void MediaPipeline::DisconnectTransport_s(TransportInfo &info) {
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
@@ -383,17 +383,17 @@ public:
                         const std::string& track_id,
                         int level,
                         bool is_video,
                         RefPtr<MediaSessionConduit> conduit,
                         RefPtr<TransportFlow> rtp_transport,
                         RefPtr<TransportFlow> rtcp_transport,
                         nsAutoPtr<MediaPipelineFilter> filter) :
       MediaPipeline(pc, TRANSMIT, main_thread, sts_thread,
-                    domstream->GetStream(), track_id, level,
+                    domstream->GetOwnedStream(), track_id, level,
                     conduit, rtp_transport, rtcp_transport, filter),
       listener_(new PipelineListener(conduit)),
       domstream_(domstream),
       is_video_(is_video)
   {}
 
   // Initialize (stuff here may fail)
   virtual nsresult Init() override;
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -456,31 +456,31 @@ MediaPipelineFactory::CreateMediaPipelin
   MOZ_MTLOG(ML_DEBUG, __FUNCTION__ << ": Creating pipeline for "
             << numericTrackId << " -> " << aTrack.GetTrackId());
 
   if (aTrack.GetMediaType() == SdpMediaSection::kAudio) {
     pipeline = new MediaPipelineReceiveAudio(
         mPC->GetHandle(),
         mPC->GetMainThread().get(),
         mPC->GetSTSThread(),
-        stream->GetMediaStream()->GetStream(),
+        stream->GetMediaStream()->GetInputStream(),
         aTrack.GetTrackId(),
         numericTrackId,
         aLevel,
         static_cast<AudioSessionConduit*>(aConduit.get()), // Ugly downcast.
         aRtpFlow,
         aRtcpFlow,
         aFilter,
         queue_track);
   } else if (aTrack.GetMediaType() == SdpMediaSection::kVideo) {
     pipeline = new MediaPipelineReceiveVideo(
         mPC->GetHandle(),
         mPC->GetMainThread().get(),
         mPC->GetSTSThread(),
-        stream->GetMediaStream()->GetStream(),
+        stream->GetMediaStream()->GetInputStream(),
         aTrack.GetTrackId(),
         numericTrackId,
         aLevel,
         static_cast<VideoSessionConduit*>(aConduit.get()), // Ugly downcast.
         aRtpFlow,
         aRtcpFlow,
         aFilter,
         queue_track);
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -237,17 +237,17 @@ public:
                     jrv.ErrorCodeAsInt());
       }
     }
 
     if (notifyStream) {
       // Start currentTime from the point where this stream was successfully
       // returned.
       aStream->SetLogicalStreamStartTime(
-          aStream->GetStream()->GetCurrentTime());
+          aStream->GetPlaybackStream()->GetCurrentTime());
 
       JSErrorResult rv;
       CSFLogInfo(logTag, "Calling OnAddStream(%s)", streamId.c_str());
       mObserver->OnAddStream(*aStream, rv);
       if (rv.Failed()) {
         CSFLogError(logTag, ": OnAddStream() failed! Error: %u",
                     rv.ErrorCodeAsInt());
       }
@@ -466,17 +466,17 @@ PeerConnectionImpl::MakeMediaStream()
     // we're either certain that we need isolation for the streams, OR
     // we're not sure and we can fix the stream in SetDtlsConnected
     nsCOMPtr<nsIPrincipal> principal =
       do_CreateInstance(NS_NULLPRINCIPAL_CONTRACTID);
     stream->CombineWithPrincipal(principal);
   }
 #endif
 
-  CSFLogDebug(logTag, "Created media stream %p, inner: %p", stream.get(), stream->GetStream());
+  CSFLogDebug(logTag, "Created media stream %p, inner: %p", stream.get(), stream->GetInputStream());
 
   return stream.forget();
 }
 
 nsresult
 PeerConnectionImpl::CreateRemoteSourceStreamInfo(nsRefPtr<RemoteSourceStreamInfo>*
                                                  aInfo,
                                                  const std::string& aStreamID)
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -1299,17 +1299,17 @@ void
 RemoteSourceStreamInfo::StartReceiving()
 {
   if (mReceiving || mPipelines.empty()) {
     return;
   }
 
   mReceiving = true;
 
-  SourceMediaStream* source = GetMediaStream()->GetStream()->AsSourceStream();
+  SourceMediaStream* source = GetMediaStream()->GetInputStream()->AsSourceStream();
   source->FinishAddTracks();
   source->SetPullEnabled(true);
   // AdvanceKnownTracksTicksTime(HEAT_DEATH_OF_UNIVERSE) means that in
   // theory per the API, we can't add more tracks before that
   // time. However, the impl actually allows it, and it avoids a whole
   // bunch of locking that would be required (and potential blocking)
   // if we used smaller values and updated them on each NotifyPull.
   source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
--- a/media/webrtc/signaling/test/FakeMediaStreams.h
+++ b/media/webrtc/signaling/test/FakeMediaStreams.h
@@ -361,16 +361,19 @@ public:
     return ds.forget();
   }
 
   virtual void Stop() {} // Really DOMLocalMediaStream
 
   virtual bool AddDirectListener(Fake_MediaStreamListener *aListener) { return false; }
   virtual void RemoveDirectListener(Fake_MediaStreamListener *aListener) {}
 
+  Fake_MediaStream *GetInputStream() { return mMediaStream; }
+  Fake_MediaStream *GetOwnedStream() { return mMediaStream; }
+  Fake_MediaStream *GetPlaybackStream() { return mMediaStream; }
   Fake_MediaStream *GetStream() { return mMediaStream; }
   std::string GetId() const { return mID; }
   void AssignId(const std::string& id) { mID = id; }
 
   // Hints to tell the SDP generator about whether this
   // MediaStream probably has audio and/or video
   typedef uint8_t TrackTypeHints;
   enum {
@@ -406,16 +409,32 @@ public:
   HasTrack(const Fake_MediaStreamTrack& aTrack) const
   {
     return ((mHintContents & HINT_CONTENTS_AUDIO) && aTrack.AsAudioStreamTrack()) ||
            ((mHintContents & HINT_CONTENTS_VIDEO) && aTrack.AsVideoStreamTrack());
   }
 
   void SetTrackEnabled(mozilla::TrackID aTrackID, bool aEnabled) {}
 
+  Fake_MediaStreamTrack*
+  CreateOwnDOMTrack(mozilla::TrackID aTrackID, mozilla::MediaSegment::Type aType)
+  {
+    switch(aType) {
+      case mozilla::MediaSegment::AUDIO: {
+        return mAudioTrack;
+      }
+      case mozilla::MediaSegment::VIDEO: {
+        return mVideoTrack;
+      }
+      default: {
+        MOZ_CRASH("Unkown media type");
+      }
+    }
+  }
+
   class PrincipalChangeObserver
   {
   public:
     virtual void PrincipalChanged(Fake_DOMMediaStream* aMediaStream) = 0;
   };
   void AddPrincipalChangeObserver(void* ignoredObserver) {}
   void RemovePrincipalChangeObserver(void* ignoredObserver) {}