Bug 1208371 - Add a MediaStreamTrackSource interface. r?roc draft
authorAndreas Pehrson <pehrsons@gmail.com>
Fri, 22 Jan 2016 15:08:12 +0800
changeset 342089 13b5bd00dc0a90b93a1d36c5a966348c40f87876
parent 342088 9cb8ede409295cb01f8dbfeb16296b7388bb1cec
child 342090 fc6b0c796741d6cb94bf0bf6ff4e5ce8c54adc06
push id13352
push userpehrsons@gmail.com
push dateFri, 18 Mar 2016 13:49:47 +0000
reviewersroc
bugs1208371
milestone47.0a1
Bug 1208371 - Add a MediaStreamTrackSource interface. r?roc This lets a MediaStreamTrack communicate with its source/producer on the main thread. It's for now used for stopping a track at the source and retrieving some metadata, but it could also be a link between actual sinks of a track and the source, to for instance let the source optimize by scaling down the resolution when all sinks want lowres-video. MozReview-Commit-ID: D4SJLr0aqhJ
dom/camera/DOMCameraControl.cpp
dom/html/HTMLCanvasElement.cpp
dom/html/HTMLMediaElement.cpp
dom/media/AudioStreamTrack.h
dom/media/DOMMediaStream.cpp
dom/media/DOMMediaStream.h
dom/media/MediaManager.cpp
dom/media/MediaStreamTrack.cpp
dom/media/MediaStreamTrack.h
dom/media/VideoStreamTrack.h
dom/media/imagecapture/ImageCapture.cpp
dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/test/FakeMediaStreams.h
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -524,17 +524,18 @@ nsDOMCameraControl::GetCameraStream() co
   return mInput;
 }
 
 void
 nsDOMCameraControl::TrackCreated(TrackID aTrackID) {
   // This track is not connected through a port.
   MediaInputPort* inputPort = nullptr;
   dom::VideoStreamTrack* track =
-    new dom::VideoStreamTrack(this, aTrackID, nsString());
+    new dom::VideoStreamTrack(this, aTrackID, nsString(),
+                              new BasicUnstoppableTrackSource());
   RefPtr<TrackPort> port =
     new TrackPort(inputPort, track,
                   TrackPort::InputPortOwnership::OWNED);
   mTracks.AppendElement(port.forget());
   NotifyTrackAdded(track);
 }
 
 #define THROW_IF_NO_CAMERACONTROL(...)                                          \
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -13,16 +13,17 @@
 #include "MediaSegment.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Base64.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/CanvasCaptureMediaStream.h"
 #include "mozilla/dom/CanvasRenderingContext2D.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/HTMLCanvasElementBinding.h"
+#include "mozilla/dom/MediaStreamTrack.h"
 #include "mozilla/dom/MouseEvent.h"
 #include "mozilla/dom/OffscreenCanvas.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/layers/AsyncCanvasRenderer.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
@@ -677,17 +678,18 @@ HTMLCanvasElement::CaptureStream(const O
 
   TrackID videoTrackId = 1;
   nsresult rv = stream->Init(aFrameRate, videoTrackId);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
-  stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString());
+  stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString(),
+                            new BasicUnstoppableTrackSource());
 
   rv = RegisterFrameCaptureListener(stream->FrameCaptureListener());
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
   return stream.forget();
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1900,21 +1900,25 @@ HTMLMediaElement::CaptureStreamInternal(
   mAudioCaptured = true;
   if (mDecoder) {
     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->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO, nsString());
+        RefPtr<MediaStreamTrackSource> trackSource = new BasicUnstoppableTrackSource();
+        out->mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO,
+                                        nsString(), trackSource);
       }
       if (HasVideo()) {
         TrackID videoTrackId = mMediaInfo.mVideo.mTrackId;
-        out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString());
+        RefPtr<MediaStreamTrackSource> trackSource = new BasicUnstoppableTrackSource();
+        out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO,
+                                        nsString(), trackSource);
       }
     }
   }
   RefPtr<DOMMediaStream> result = out->mStream;
   return result.forget();
 }
 
 already_AddRefed<DOMMediaStream>
--- a/dom/media/AudioStreamTrack.h
+++ b/dom/media/AudioStreamTrack.h
@@ -9,18 +9,19 @@
 #include "MediaStreamTrack.h"
 #include "DOMMediaStream.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioStreamTrack : public MediaStreamTrack {
 public:
-  AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
-    : MediaStreamTrack(aStream, aTrackID, aLabel) {}
+  AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
+                   const nsString& aLabel, MediaStreamTrackSource* aSource)
+    : MediaStreamTrack(aStream, aTrackID, aLabel, aSource) {}
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   AudioStreamTrack* AsAudioStreamTrack() override { return this; }
 
   // WebIDL
   void GetKind(nsAString& aKind) override { aKind.AssignLiteral("audio"); }
 };
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -116,25 +116,26 @@ public:
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mStream) {
       return;
     }
 
     MediaStreamTrack* track = mStream->FindOwnedDOMTrack(
       mStream->GetOwnedStream(), aTrackId);
-    if (track) {
-      // This track has already been manually created. Abort.
-      return;
+    if (!track) {
+      // Track had not been created on main thread before, create it now.
+      NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(),
+                       "A new track was detected on the input stream; creating "
+                       "a corresponding MediaStreamTrack. Initial tracks "
+                       "should be added manually to immediately and "
+                       "synchronously be available to JS.");
+      track = mStream->CreateOwnDOMTrack(aTrackId, aType, nsString(),
+                                         new BasicUnstoppableTrackSource());
     }
-
-    NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(),
-                     "A new track was detected on the input stream; creating a corresponding MediaStreamTrack. "
-                     "Initial tracks should be added manually to immediately and synchronously be available to JS.");
-    mStream->CreateOwnDOMTrack(aTrackId, aType, nsString());
   }
 
   void DoNotifyTrackEnded(TrackID aTrackId)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mStream) {
       return;
@@ -605,22 +606,25 @@ DOMMediaStream::InitTrackUnionStream(Med
   InitPlaybackStreamCommon(aGraph);
 }
 
 void
 DOMMediaStream::InitAudioCaptureStream(MediaStreamGraph* aGraph)
 {
   const TrackID AUDIO_TRACK = 1;
 
+  RefPtr<BasicUnstoppableTrackSource> audioCaptureSource =
+    new BasicUnstoppableTrackSource(MediaSourceEnum::AudioCapture);
+
   AudioCaptureStream* audioCaptureStream =
     static_cast<AudioCaptureStream*>(aGraph->CreateAudioCaptureStream(this, AUDIO_TRACK));
   InitInputStreamCommon(audioCaptureStream, aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
-  CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, nsString());
+  CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, nsString(), audioCaptureSource);
   audioCaptureStream->Start();
 }
 
 void
 DOMMediaStream::InitInputStreamCommon(MediaStream* aStream,
                                       MediaStreamGraph* aGraph)
 {
   MOZ_ASSERT(!mOwnedStream, "Input stream must be initialized before owned stream");
@@ -693,24 +697,16 @@ void
 DOMMediaStream::SetTrackEnabled(TrackID aTrackID, bool aEnabled)
 {
   // XXX Bug 1208371 - This enables/disables the track across clones.
   if (mInputStream) {
     mInputStream->SetTrackEnabled(aTrackID, aEnabled);
   }
 }
 
-void
-DOMMediaStream::StopTrack(TrackID aTrackID)
-{
-  if (mInputStream && mInputStream->AsSourceStream()) {
-    mInputStream->AsSourceStream()->EndTrack(aTrackID);
-  }
-}
-
 already_AddRefed<Promise>
 DOMMediaStream::ApplyConstraintsToTrack(TrackID aTrackID,
                                         const MediaTrackConstraints& aConstraints,
                                         ErrorResult &aRv)
 {
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
   RefPtr<Promise> promise = Promise::Create(go, aRv);
   MOZ_RELEASE_ASSERT(!aRv.Failed());
@@ -772,30 +768,32 @@ DOMMediaStream::AddPrincipalChangeObserv
 
 bool
 DOMMediaStream::RemovePrincipalChangeObserver(PrincipalChangeObserver* aObserver)
 {
   return mPrincipalChangeObservers.RemoveElement(aObserver);
 }
 
 MediaStreamTrack*
-DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType, const nsString& aLabel)
+DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType,
+                                  const nsString& aLabel,
+                                  MediaStreamTrackSource* aSource)
 {
   MOZ_RELEASE_ASSERT(mInputStream);
   MOZ_RELEASE_ASSERT(mOwnedStream);
 
   MOZ_ASSERT(FindOwnedDOMTrack(GetOwnedStream(), aTrackID) == nullptr);
 
   MediaStreamTrack* track;
   switch (aType) {
   case MediaSegment::AUDIO:
-    track = new AudioStreamTrack(this, aTrackID, aLabel);
+    track = new AudioStreamTrack(this, aTrackID, aLabel, aSource);
     break;
   case MediaSegment::VIDEO:
-    track = new VideoStreamTrack(this, aTrackID, aLabel);
+    track = new VideoStreamTrack(this, aTrackID, aLabel, aSource);
     break;
   default:
     MOZ_CRASH("Unhandled track type");
   }
 
   LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p with ID %u", this, track, aTrackID));
 
   RefPtr<TrackPort> ownedTrackPort =
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -30,27 +30,27 @@
 #endif
 
 namespace mozilla {
 
 class DOMHwMediaStream;
 class DOMLocalMediaStream;
 class DOMMediaStream;
 class MediaStream;
-class MediaEngineSource;
 class MediaInputPort;
 class MediaStreamGraph;
 class ProcessedMediaStream;
 
 namespace dom {
 class AudioNode;
 class HTMLCanvasElement;
 class MediaStreamTrack;
 class AudioStreamTrack;
 class VideoStreamTrack;
+class MediaStreamTrackSource;
 class AudioTrack;
 class VideoTrack;
 class AudioTrackList;
 class VideoTrackList;
 class MediaTrackListListener;
 struct MediaTrackConstraints;
 } // namespace dom
 
@@ -179,16 +179,17 @@ public:
  *                                                     (pointing to t2 in A')
  */
 class DOMMediaStream : public DOMEventTargetHelper
 {
   friend class DOMLocalMediaStream;
   typedef dom::MediaStreamTrack MediaStreamTrack;
   typedef dom::AudioStreamTrack AudioStreamTrack;
   typedef dom::VideoStreamTrack VideoStreamTrack;
+  typedef dom::MediaStreamTrackSource MediaStreamTrackSource;
   typedef dom::AudioTrack AudioTrack;
   typedef dom::VideoTrack VideoTrack;
   typedef dom::AudioTrackList AudioTrackList;
   typedef dom::VideoTrackList VideoTrackList;
 
 public:
   typedef dom::MediaTrackConstraints MediaTrackConstraints;
 
@@ -376,18 +377,16 @@ public:
   virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) {}
 
   /**
    * Overridden in DOMLocalMediaStreams to allow getUserMedia to disable
    * media at the SourceMediaStream.
    */
   virtual void SetTrackEnabled(TrackID aTrackID, bool aEnabled);
 
-  virtual void StopTrack(TrackID aTrackID);
-
   virtual already_AddRefed<dom::Promise>
   ApplyConstraintsToTrack(TrackID aTrackID,
                           const MediaTrackConstraints& aConstraints,
                           ErrorResult &aRv);
 
   virtual DOMLocalMediaStream* AsDOMLocalMediaStream() { return nullptr; }
   virtual DOMHwMediaStream* AsDOMHwMediaStream() { return nullptr; }
 
@@ -478,17 +477,19 @@ public:
   }
 
   /**
    * 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, const nsString& aLabel);
+  MediaStreamTrack* CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType,
+                                      const nsString& aLabel,
+                                      MediaStreamTrackSource* aSource);
 
   // When the initial set of tracks has been added, run
   // aCallback->NotifyTracksAvailable.
   // It is allowed to do anything, including run script.
   // aCallback may run immediately during this call if tracks are already
   // available!
   // We only care about track additions, we'll fire the notification even if
   // some of the tracks have been removed.
@@ -636,18 +637,16 @@ public:
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOMLOCALMEDIASTREAM_IID)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void Stop();
 
-  virtual MediaEngineSource* GetMediaEngine(TrackID aTrackID) { return nullptr; }
-
   /**
    * Create an nsDOMLocalMediaStream whose underlying stream is a SourceMediaStream.
    */
   static already_AddRefed<DOMLocalMediaStream>
   CreateSourceStream(nsPIDOMWindowInner* aWindow,
                      MediaStreamGraph* aGraph);
 
   /**
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -661,67 +661,39 @@ nsresult AudioDevice::Restart(const dom:
  * that need to be cleaned up.
  */
 class nsDOMUserMediaStream : public DOMLocalMediaStream
 {
 public:
   static already_AddRefed<nsDOMUserMediaStream>
   CreateSourceStream(nsPIDOMWindowInner* aWindow,
                      GetUserMediaCallbackMediaStreamListener* aListener,
-                     AudioDevice* aAudioDevice,
-                     VideoDevice* aVideoDevice,
                      MediaStreamGraph* aMSG)
   {
     RefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aWindow,
-                                                                   aListener,
-                                                                   aAudioDevice,
-                                                                   aVideoDevice);
+                                                                   aListener);
     stream->InitSourceStream(aMSG);
     return stream.forget();
   }
 
   nsDOMUserMediaStream(nsPIDOMWindowInner* aWindow,
-                       GetUserMediaCallbackMediaStreamListener* aListener,
-                       AudioDevice *aAudioDevice,
-                       VideoDevice *aVideoDevice) :
+                       GetUserMediaCallbackMediaStreamListener* aListener) :
     DOMLocalMediaStream(aWindow),
-    mListener(aListener),
-    mAudioDevice(aAudioDevice),
-    mVideoDevice(aVideoDevice)
+    mListener(aListener)
   {}
 
   virtual ~nsDOMUserMediaStream()
   {
     StopImpl();
 
     if (GetSourceStream()) {
       GetSourceStream()->Destroy();
     }
   }
 
-  // For gUM streams, we have a trackunion which assigns TrackIDs.  However, for a
-  // single-source trackunion like we have here, the TrackUnion will assign trackids
-  // that match the source's trackids, so we can avoid needing a mapping function.
-  // XXX This will not handle more complex cases well.
-  void StopTrack(TrackID aTrackID) override
-  {
-    if (GetSourceStream()) {
-      GetSourceStream()->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.
-      RefPtr<dom::MediaStreamTrack> ownedTrack = FindOwnedDOMTrack(mOwnedStream, aTrackID);
-      if (ownedTrack) {
-        mListener->StopTrack(aTrackID);
-      } else {
-        LOG(("StopTrack(%d) on non-existent track", aTrackID));
-      }
-    }
-  }
-
   already_AddRefed<Promise>
   ApplyConstraintsToTrack(TrackID aTrackID,
                           const MediaTrackConstraints& aConstraints,
                           ErrorResult &aRv) override
   {
     nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
     RefPtr<Promise> promise = Promise::Create(go, aRv);
 
@@ -779,41 +751,25 @@ public:
     }
   }
 
   DOMLocalMediaStream* AsDOMLocalMediaStream() override
   {
     return this;
   }
 
-  MediaEngineSource* GetMediaEngine(TrackID aTrackID) override
-  {
-    // MediaEngine supports only one video and on video track now and TrackID is
-    // fixed in MediaEngine.
-    if (aTrackID == kVideoTrack) {
-      return mVideoDevice ? mVideoDevice->GetSource() : nullptr;
-    }
-    else if (aTrackID == kAudioTrack) {
-      return mAudioDevice ? mAudioDevice->GetSource() : nullptr;
-    }
-
-    return nullptr;
-  }
-
   SourceMediaStream* GetSourceStream()
   {
     if (GetInputStream()) {
       return GetInputStream()->AsSourceStream();
     }
     return nullptr;
   }
 
   RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
-  RefPtr<AudioDevice> mAudioDevice; // so we can turn on AEC
-  RefPtr<VideoDevice> mVideoDevice;
 };
 
 
 void
 MediaOperationTask::ReturnCallbackError(nsresult rv, const char* errorLog)
 {
   MM_LOG(("%s , rv=%d", errorLog, rv));
   NS_DispatchToMainThread(do_AddRef(new ReleaseMediaOperationResource(mStream.forget(),
@@ -944,31 +900,70 @@ public:
       // 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());
       stream = msg->CreateSourceStream(nullptr); // Placeholder
       msg->RegisterCaptureStreamForWindow(
             mWindowID, domStream->GetInputStream()->AsProcessedStream());
       window->SetAudioCapture(true);
     } else {
+      class LocalTrackSource : public MediaStreamTrackSource
+      {
+      public:
+        LocalTrackSource(GetUserMediaCallbackMediaStreamListener* aListener,
+                         const MediaSourceEnum aSource,
+                         const TrackID aTrackID)
+          : MediaStreamTrackSource(false), mListener(aListener),
+            mSource(aSource), mTrackID(aTrackID) {}
+
+        MediaSourceEnum GetMediaSource() const override
+        {
+          return mSource;
+        }
+
+        void Stop() override
+        {
+          if (mListener) {
+            mListener->StopTrack(mTrackID);
+            mListener = nullptr;
+          }
+        }
+
+      protected:
+        ~LocalTrackSource() {}
+
+        RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
+        const MediaSourceEnum mSource;
+        const TrackID mTrackID;
+      };
+
       // Normal case, connect the source stream to the track union stream to
       // avoid us blocking
       domStream = nsDOMUserMediaStream::CreateSourceStream(window, mListener,
-                                                           mAudioDevice, mVideoDevice,
                                                            msg);
 
       if (mAudioDevice) {
         nsString audioDeviceName;
         mAudioDevice->GetName(audioDeviceName);
-        domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioDeviceName);
+        const MediaSourceEnum source =
+          mAudioDevice->GetSource()->GetMediaSource();
+        RefPtr<MediaStreamTrackSource> audioSource =
+          new LocalTrackSource(mListener, source, kAudioTrack);
+        domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO,
+                                     audioDeviceName, audioSource);
       }
       if (mVideoDevice) {
         nsString videoDeviceName;
         mVideoDevice->GetName(videoDeviceName);
-        domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoDeviceName);
+        const MediaSourceEnum source =
+          mVideoDevice->GetSource()->GetMediaSource();
+        RefPtr<MediaStreamTrackSource> videoSource =
+          new LocalTrackSource(mListener, source, kVideoTrack);
+        domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO,
+                                     videoDeviceName, videoSource);
       }
 
       nsCOMPtr<nsIPrincipal> principal;
       if (mPeerIdentity) {
         principal = nsNullPrincipal::Create();
         domStream->SetPeerIdentity(mPeerIdentity.forget());
       } else {
         principal = window->GetExtantDoc()->NodePrincipal();
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -14,24 +14,37 @@
 #endif
 
 static PRLogModuleInfo* gMediaStreamTrackLog;
 #define LOG(type, msg) MOZ_LOG(gMediaStreamTrackLog, type, msg)
 
 namespace mozilla {
 namespace dom {
 
-MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
-  : mOwningStream(aStream), mTrackID(aTrackID), mLabel(aLabel), mEnded(false), mEnabled(true)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackSource)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTrackSource)
+
+MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
+                                   const nsString& aLabel,
+                                   MediaStreamTrackSource* aSource)
+  : mOwningStream(aStream), mTrackID(aTrackID), mLabel(aLabel), mSource(aSource),
+    mEnded(false), mEnabled(true), mRemote(aSource->IsRemote()), mStopped(false)
 {
 
   if (!gMediaStreamTrackLog) {
     gMediaStreamTrackLog = PR_NewLogModule("MediaStreamTrack");
   }
 
+  MOZ_RELEASE_ASSERT(mSource);
+  mSource->RegisterSink();
+
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
 
   nsID uuid;
   memset(&uuid, 0, sizeof(uuid));
   if (uuidgen) {
     uuidgen->GenerateUUIDInPlace(&uuid);
@@ -41,18 +54,32 @@ MediaStreamTrack::MediaStreamTrack(DOMMe
   uuid.ToProvidedString(chars);
   mID = NS_ConvertASCIItoUTF16(chars);
 }
 
 MediaStreamTrack::~MediaStreamTrack()
 {
 }
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaStreamTrack, DOMEventTargetHelper,
-                                   mOwningStream, mOriginalTrack)
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
+                                                DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningStream)
+  tmp->mSource->UnregisterSink();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalTrack)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
+                                                  DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwningStream)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalTrack)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 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
@@ -70,17 +97,33 @@ MediaStreamTrack::SetEnabled(bool aEnabl
   mOwningStream->SetTrackEnabled(mTrackID, aEnabled);
 }
 
 void
 MediaStreamTrack::Stop()
 {
   LOG(LogLevel::Info, ("MediaStreamTrack %p Stop()", this));
 
-  mOwningStream->StopTrack(mTrackID);
+  if (mStopped) {
+    LOG(LogLevel::Warning, ("MediaStreamTrack %p Already stopped", this));
+    return;
+  }
+
+  if (mRemote) {
+    LOG(LogLevel::Warning, ("MediaStreamTrack %p is remote. Can't be stopped.", this));
+    return;
+  }
+
+  if (!mSource) {
+    MOZ_ASSERT(false);
+    return;
+  }
+
+  mSource->UnregisterSink();
+  mStopped = true;
 }
 
 already_AddRefed<Promise>
 MediaStreamTrack::ApplyConstraints(const MediaTrackConstraints& aConstraints,
                                    ErrorResult &aRv)
 {
   if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
     nsString str;
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -2,39 +2,149 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MEDIASTREAMTRACK_H_
 #define MEDIASTREAMTRACK_H_
 
 #include "mozilla/DOMEventTargetHelper.h"
+#include "nsError.h"
 #include "nsID.h"
 #include "StreamBuffer.h"
 #include "MediaTrackConstraints.h"
 
 namespace mozilla {
 
 class DOMMediaStream;
+class MediaEnginePhotoCallback;
 
 namespace dom {
 
 class AudioStreamTrack;
 class VideoStreamTrack;
 
 /**
+ * Common interface through which a MediaStreamTrack can communicate with its
+ * producer on the main thread.
+ *
+ * Kept alive by a strong ref in all MediaStreamTracks (original and clones)
+ * sharing this source.
+ */
+class MediaStreamTrackSource : public nsISupports
+{
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(MediaStreamTrackSource)
+
+public:
+  explicit MediaStreamTrackSource(const bool aIsRemote)
+    : mNrSinks(0), mIsRemote(aIsRemote), mStopped(false)
+  {
+    MOZ_COUNT_CTOR(MediaStreamTrackSource);
+  }
+
+  /**
+   * Gets the source's MediaSourceEnum for usage by PeerConnections.
+   */
+  virtual MediaSourceEnum GetMediaSource() const = 0;
+
+  /**
+   * Indicates whether the track is remote or not per the MediaCapture and
+   * Streams spec.
+   */
+  virtual bool IsRemote() const { return mIsRemote; }
+
+  /**
+   * Forwards a photo request to backends that support it. Other backends return
+   * NS_ERROR_NOT_IMPLEMENTED to indicate that a MediaStreamGraph-based fallback
+   * should be used.
+   */
+  virtual nsresult TakePhoto(MediaEnginePhotoCallback*) const { return NS_ERROR_NOT_IMPLEMENTED; }
+
+  /**
+   * Called by the source interface when all registered sinks have unregistered.
+   */
+  virtual void Stop() = 0;
+
+  /**
+   * Called by each MediaStreamTrack clone on initialization.
+   */
+  void RegisterSink()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (mStopped) {
+      return;
+    }
+    ++mNrSinks;
+  }
+
+  /**
+   * Called by each MediaStreamTrack clone on track.Stop().
+   */
+  void UnregisterSink()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    NS_ASSERTION(mNrSinks > 0, "Unmatched UnregisterSink()");
+    --mNrSinks;
+    if (mNrSinks == 0 && !IsRemote()) {
+      Stop();
+      mStopped = true;
+    }
+  }
+
+protected:
+  virtual ~MediaStreamTrackSource()
+  {
+    MOZ_COUNT_DTOR(MediaStreamTrackSource);
+    NS_ASSERTION(mNrSinks == 0, "Some sinks did not unregister");
+  }
+
+  // Number of currently registered sinks.
+  size_t mNrSinks;
+
+  // True if this is a remote track source, i.e., a PeerConnection.
+  const bool mIsRemote;
+
+  // True if this source is not remote, all MediaStreamTrack users have
+  // unregistered from this source and Stop() has been called.
+  bool mStopped;
+};
+
+/**
+ * Basic implementation of MediaStreamTrackSource that ignores Stop().
+ */
+class BasicUnstoppableTrackSource : public MediaStreamTrackSource
+{
+public:
+  explicit BasicUnstoppableTrackSource(const MediaSourceEnum aMediaSource =
+                                         MediaSourceEnum::Other)
+    : MediaStreamTrackSource(true), mMediaSource(aMediaSource) {}
+
+  MediaSourceEnum GetMediaSource() const override { return mMediaSource; }
+
+  void Stop() override {}
+
+protected:
+  ~BasicUnstoppableTrackSource() {}
+
+  const MediaSourceEnum mMediaSource;
+};
+
+/**
  * Class representing a track in a DOMMediaStream.
  */
 class MediaStreamTrack : public DOMEventTargetHelper {
 public:
   /**
    * aTrackID is the MediaStreamGraph track ID for the track in the
    * MediaStream owned by aStream.
    */
-  MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel);
+  MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
+                   const nsString& aLabel,
+                   MediaStreamTrackSource* aSource);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrack,
                                            DOMEventTargetHelper)
 
   DOMMediaStream* GetParentObject() const { return mOwningStream; }
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override = 0;
 
@@ -60,28 +170,38 @@ public:
   void Stop();
   already_AddRefed<Promise>
   ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv);
 
   bool Ended() const { return mEnded; }
   // Notifications from the MediaStreamGraph
   void NotifyEnded() { mEnded = true; }
 
+  MediaStreamTrackSource& GetSource() const
+  {
+    MOZ_RELEASE_ASSERT(mSource, "The track source is only removed on destruction");
+    return *mSource;
+  }
+
   // 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();
 
   RefPtr<DOMMediaStream> mOwningStream;
   TrackID mTrackID;
+  TrackID mInputTrackID;
+  RefPtr<MediaStreamTrackSource> mSource;
   RefPtr<MediaStreamTrack> mOriginalTrack;
   nsString mID;
   nsString mLabel;
   bool mEnded;
   bool mEnabled;
+  const bool mRemote;
+  bool mStopped;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* MEDIASTREAMTRACK_H_ */
--- a/dom/media/VideoStreamTrack.h
+++ b/dom/media/VideoStreamTrack.h
@@ -9,18 +9,20 @@
 #include "MediaStreamTrack.h"
 #include "DOMMediaStream.h"
 
 namespace mozilla {
 namespace dom {
 
 class VideoStreamTrack : public MediaStreamTrack {
 public:
-  VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
-    : MediaStreamTrack(aStream, aTrackID, aLabel) {}
+  VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
+                   const nsString& aLabel,
+                   MediaStreamTrackSource* aSource)
+    : MediaStreamTrack(aStream, aTrackID, aLabel, aSource) {}
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   VideoStreamTrack* AsVideoStreamTrack() override { return this; }
 
   // WebIDL
   void GetKind(nsAString& aKind) override { aKind.AssignLiteral("video"); }
 };
--- a/dom/media/imagecapture/ImageCapture.cpp
+++ b/dom/media/imagecapture/ImageCapture.cpp
@@ -76,23 +76,24 @@ nsresult
 ImageCapture::TakePhotoByMediaEngine()
 {
   // Callback for TakPhoto(), it also monitor the principal. If principal
   // changes, it returns PHOTO_ERROR with security error.
   class TakePhotoCallback : public MediaEnginePhotoCallback,
                             public DOMMediaStream::PrincipalChangeObserver
   {
   public:
-    TakePhotoCallback(DOMMediaStream* aStream, ImageCapture* aImageCapture)
-      : mStream(aStream)
+    TakePhotoCallback(VideoStreamTrack* aVideoTrack, ImageCapture* aImageCapture)
+      : mVideoTrack(aVideoTrack)
       , mImageCapture(aImageCapture)
       , mPrincipalChanged(false)
     {
       MOZ_ASSERT(NS_IsMainThread());
-      mStream->AddPrincipalChangeObserver(this);
+      MOZ_RELEASE_ASSERT(mVideoTrack->GetStream());
+      mVideoTrack->GetStream()->AddPrincipalChangeObserver(this);
     }
 
     void PrincipalChanged(DOMMediaStream* aMediaStream) override
     {
       mPrincipalChanged = true;
     }
 
     nsresult PhotoComplete(already_AddRefed<Blob> aBlob) override
@@ -109,35 +110,28 @@ ImageCapture::TakePhotoByMediaEngine()
     {
       return mImageCapture->PostErrorEvent(ImageCaptureError::PHOTO_ERROR, aRv);
     }
 
   protected:
     ~TakePhotoCallback()
     {
       MOZ_ASSERT(NS_IsMainThread());
-      mStream->RemovePrincipalChangeObserver(this);
+      MOZ_RELEASE_ASSERT(mVideoTrack->GetStream());
+      mVideoTrack->GetStream()->RemovePrincipalChangeObserver(this);
     }
 
-    RefPtr<DOMMediaStream> mStream;
+    RefPtr<VideoStreamTrack> mVideoTrack;
     RefPtr<ImageCapture> mImageCapture;
     bool mPrincipalChanged;
   };
 
-  RefPtr<DOMMediaStream> domStream = mVideoStreamTrack->GetStream();
-  DOMLocalMediaStream* domLocalStream = domStream->AsDOMLocalMediaStream();
-  if (domLocalStream) {
-    RefPtr<MediaEngineSource> mediaEngine =
-      domLocalStream->GetMediaEngine(mVideoStreamTrack->GetTrackID());
-    RefPtr<MediaEnginePhotoCallback> callback =
-      new TakePhotoCallback(domStream, this);
-    return mediaEngine->TakePhoto(callback);
-  }
-
-  return NS_ERROR_NOT_IMPLEMENTED;
+  RefPtr<MediaEnginePhotoCallback> callback =
+    new TakePhotoCallback(mVideoStreamTrack, this);
+  return mVideoStreamTrack->GetSource().TakePhoto(callback);
 }
 
 void
 ImageCapture::TakePhoto(ErrorResult& aResult)
 {
   // According to spec, VideoStreamTrack.readyState must be "live"; however
   // gecko doesn't implement it yet (bug 910249). Instead of readyState, we
   // check VideoStreamTrack.enable before bug 910249 is fixed.
--- a/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaStreamAudioDestinationNode.h"
 #include "nsIDocument.h"
 #include "mozilla/dom/MediaStreamAudioDestinationNodeBinding.h"
 #include "AudioNodeEngine.h"
 #include "AudioNodeStream.h"
 #include "DOMMediaStream.h"
+#include "MediaStreamTrack.h"
 #include "TrackUnionStream.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaStreamAudioDestinationNode, AudioNode, mDOMStream)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaStreamAudioDestinationNode)
@@ -29,17 +30,20 @@ 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->CreateOwnDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO, nsString());
+  RefPtr<MediaStreamTrackSource> source =
+    new BasicUnstoppableTrackSource(MediaSourceEnum::AudioCapture);
+  mDOMStream->CreateOwnDOMTrack(AudioNodeStream::AUDIO_TRACK,
+                                MediaSegment::AUDIO, nsString(), source);
 
   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);
 
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -844,20 +844,17 @@ MediaPipelineFactory::ConfigureVideoCode
   RefPtr<DOMMediaStream> mediastream =
     mPCMedia->GetLocalStreamById(aTrack.GetStreamId())->GetMediaStream();
 
   DOMLocalMediaStream* domLocalStream = mediastream->AsDOMLocalMediaStream();
   if (!domLocalStream) {
     return NS_OK;
   }
 
-  MediaEngineSource *engine =
-    domLocalStream->GetMediaEngine(videotrack->GetTrackID());
-
-  dom::MediaSourceEnum source = engine->GetMediaSource();
+  dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource();
   webrtc::VideoCodecMode mode = webrtc::kRealtimeVideo;
   switch (source) {
     case dom::MediaSourceEnum::Browser:
     case dom::MediaSourceEnum::Screen:
     case dom::MediaSourceEnum::Application:
     case dom::MediaSourceEnum::Window:
       mode = webrtc::kScreensharing;
       break;
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1874,16 +1874,29 @@ PeerConnectionImpl::SetRemoteDescription
             ++numNewAudioTracks;
           } else if (track->GetMediaType() == SdpMediaSection::kVideo) {
             ++numNewVideoTracks;
           } else {
             MOZ_ASSERT(false);
             continue;
           }
           info->AddTrack(track->GetTrackId());
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+          RefPtr<MediaStreamTrackSource> source =
+            new BasicUnstoppableTrackSource(MediaSourceEnum::Other);
+          if (track->GetMediaType() == SdpMediaSection::kAudio) {
+            info->GetMediaStream()->CreateOwnDOMTrack(
+              info->GetNumericTrackId(track->GetTrackId()),
+              MediaSegment::AUDIO, nsString(), source);
+          } else {
+            info->GetMediaStream()->CreateOwnDOMTrack(
+              info->GetNumericTrackId(track->GetTrackId()),
+              MediaSegment::VIDEO, nsString(), source);
+          }
+#endif
           CSFLogDebug(logTag, "Added remote track %s/%s",
                       info->GetId().c_str(), track->GetTrackId().c_str());
         } else {
           ++numPreexistingTrackIds;
         }
       }
 
       // Now that the streams are all set up, notify about track availability.
--- a/media/webrtc/signaling/test/FakeMediaStreams.h
+++ b/media/webrtc/signaling/test/FakeMediaStreams.h
@@ -281,16 +281,24 @@ class Fake_SourceMediaStream : public Fa
   bool mStop;
   RefPtr<Fake_MediaPeriodic> mPeriodic;
   RefPtr<Fake_VideoSink> mSink;
   nsCOMPtr<nsITimer> mTimer;
 };
 
 class Fake_DOMMediaStream;
 
+class Fake_MediaStreamTrackSource
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamTrackSource)
+
+protected:
+  virtual ~Fake_MediaStreamTrackSource() {}
+};
+
 class Fake_MediaStreamTrack
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamTrack)
 
   Fake_MediaStreamTrack(bool aIsVideo, Fake_DOMMediaStream* aStream) :
     mIsVideo (aIsVideo),
     mStream (aStream)
@@ -412,17 +420,18 @@ public:
   {
     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)
+  CreateOwnDOMTrack(mozilla::TrackID aTrackID, mozilla::MediaSegment::Type aType,
+                    const nsString& aLabel, Fake_MediaStreamTrackSource* aSource)
   {
     switch(aType) {
       case mozilla::MediaSegment::AUDIO: {
         return mAudioTrack;
       }
       case mozilla::MediaSegment::VIDEO: {
         return mVideoTrack;
       }
@@ -492,11 +501,15 @@ class Fake_VideoStreamSource : public Fa
 
 namespace mozilla {
 typedef Fake_MediaStream MediaStream;
 typedef Fake_SourceMediaStream SourceMediaStream;
 typedef Fake_MediaStreamListener MediaStreamListener;
 typedef Fake_MediaStreamDirectListener MediaStreamDirectListener;
 typedef Fake_DOMMediaStream DOMMediaStream;
 typedef Fake_DOMMediaStream DOMLocalMediaStream;
+
+namespace dom {
+typedef Fake_MediaStreamTrackSource MediaStreamTrackSource;
+}
 }
 
 #endif