Bug 1103188 - Break out MediaTrackListListener to an interface. r=roc
authorAndreas Pehrson <pehrsons@gmail.com>
Wed, 30 Sep 2015 09:32:06 +0800
changeset 298590 7e11e0cb64169c636c81e3fe440f7c54460b53a6
parent 298589 46c6625476247b8779de96236ca108235a596a8a
child 298591 29309e60a4bf72fd8f374475a4ba80d543ff9b6a
push id5392
push userraliiev@mozilla.com
push dateMon, 14 Dec 2015 20:08:23 +0000
treeherdermozilla-beta@16ce8562a975 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs1103188
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1103188 - Break out MediaTrackListListener to an interface. r=roc Other modules than MediaTrackLists may want to receive updates on a DOMMediaStream's track set. This moves the MediaTrackListListener out of the MediaTrackList class into DOMMediaStream as a general interface. The logic for adding MediaTracks to the MediaTrackList when MediaStreamTracks are added or removed from a DOMMediaStream is moved to HTMLMediaElement as this fits the model better - HTMLMediaElement is the owner of the MediaTrackLists.
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/DOMMediaStream.cpp
dom/media/DOMMediaStream.h
dom/media/MediaStreamTrack.h
dom/media/MediaTrackList.cpp
dom/media/MediaTrackList.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -66,16 +66,17 @@
 #include "MediaStreamGraph.h"
 #include "nsIScriptError.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "mozilla/dom/MediaSource.h"
 #include "MediaMetadataManager.h"
 #include "MediaSourceDecoder.h"
 #include "AudioStreamTrack.h"
 #include "VideoStreamTrack.h"
+#include "MediaTrackList.h"
 
 #include "AudioChannelService.h"
 
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/WakeLock.h"
 
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
@@ -2100,16 +2101,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mMediaSecurityVerified(false),
     mCORSMode(CORS_NONE),
     mIsEncrypted(false),
     mDownloadSuspendedByCache(false, "HTMLMediaElement::mDownloadSuspendedByCache"),
     mAudioChannelVolume(1.0),
     mPlayingThroughTheAudioChannel(false),
     mDisableVideo(false),
     mPlayBlockedBecauseHidden(false),
+    mMediaStreamTrackListener(nullptr),
     mElementInTreeState(ELEMENT_NOT_INTREE),
     mHasUserInteraction(false)
 {
   if (!gMediaElementLog) {
     gMediaElementLog = PR_NewLogModule("nsMediaElement");
   }
   if (!gMediaElementEventsLog) {
     gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
@@ -3095,20 +3097,44 @@ public:
       mElement(aElement)
     {}
   virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
   {
     NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
     mElement->NotifyMediaStreamTracksAvailable(aStream);
   }
+
 private:
   HTMLMediaElement* mElement;
 };
 
+class HTMLMediaElement::MediaStreamTrackListener :
+  public DOMMediaStream::TrackListener
+{
+public:
+  explicit MediaStreamTrackListener(HTMLMediaElement* aElement):
+      mElement(aElement) {}
+
+  void NotifyTrackAdded(const nsRefPtr<MediaStreamTrack>& aTrack) override
+  {
+    mElement->NotifyMediaStreamTrackAdded(aTrack);
+  }
+
+  void NotifyTrackRemoved(const nsRefPtr<MediaStreamTrack>& aTrack) override
+  {
+    mElement->NotifyMediaStreamTrackRemoved(aTrack);
+  }
+
+protected:
+  ~MediaStreamTrackListener() {}
+
+  HTMLMediaElement* const mElement;
+};
+
 void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags)
 {
   if (!mSrcStream) {
     return;
   }
   // We might be in cycle collection with mSrcStream->GetPlaybackStream() already
   // returning null due to unlinking.
 
@@ -3193,43 +3219,148 @@ void HTMLMediaElement::SetupSrcMediaStre
 
   nsRefPtr<MediaStream> stream = GetSrcMediaStream();
   if (stream) {
     stream->SetAudioChannelType(mAudioChannel);
   }
 
   UpdateSrcMediaStreamPlaying();
 
-  // Note: we must call DisconnectTrackListListeners(...)  before dropping
-  // mSrcStream.
   // If we pause this media element, track changes in the underlying stream
   // will continue to fire events at this element and alter its track list.
   // That's simpler than delaying the events, but probably confusing...
-  mSrcStream->ConstructMediaTracks(AudioTracks(), VideoTracks());
+  ConstructMediaTracks();
 
   mSrcStream->OnTracksAvailable(new MediaStreamTracksAvailableCallback(this));
+  mMediaStreamTrackListener = new MediaStreamTrackListener(this);
+  mSrcStream->RegisterTrackListener(mMediaStreamTrackListener);
 
   ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
   ChangeDelayLoadStatus(false);
   CheckAutoplayDataReady();
 
   // FirstFrameLoaded() will be called when the stream has current data.
 }
 
 void HTMLMediaElement::EndSrcMediaStreamPlayback()
 {
   MOZ_ASSERT(mSrcStream);
 
   UpdateSrcMediaStreamPlaying(REMOVING_SRC_STREAM);
 
-  mSrcStream->DisconnectTrackListListeners(AudioTracks(), VideoTracks());
+  mSrcStream->UnregisterTrackListener(mMediaStreamTrackListener);
+  mMediaStreamTrackListener = nullptr;
 
   mSrcStream = nullptr;
 }
 
+static already_AddRefed<AudioTrack>
+CreateAudioTrack(AudioStreamTrack* aStreamTrack)
+{
+  nsAutoString id;
+  nsAutoString label;
+  aStreamTrack->GetId(id);
+  aStreamTrack->GetLabel(label);
+
+  return MediaTrackList::CreateAudioTrack(id, NS_LITERAL_STRING("main"),
+                                          label, EmptyString(),
+                                          aStreamTrack->Enabled());
+}
+
+static already_AddRefed<VideoTrack>
+CreateVideoTrack(VideoStreamTrack* aStreamTrack)
+{
+  nsAutoString id;
+  nsAutoString label;
+  aStreamTrack->GetId(id);
+  aStreamTrack->GetLabel(label);
+
+  return MediaTrackList::CreateVideoTrack(id, NS_LITERAL_STRING("main"),
+                                          label, EmptyString());
+}
+
+void HTMLMediaElement::ConstructMediaTracks()
+{
+  nsTArray<nsRefPtr<MediaStreamTrack>> tracks;
+  mSrcStream->GetTracks(tracks);
+
+  int firstEnabledVideo = -1;
+  for (const nsRefPtr<MediaStreamTrack>& track : tracks) {
+    if (track->Ended()) {
+      continue;
+    }
+
+    if (AudioStreamTrack* t = track->AsAudioStreamTrack()) {
+      nsRefPtr<AudioTrack> audioTrack = CreateAudioTrack(t);
+      AudioTracks()->AddTrack(audioTrack);
+    } else if (VideoStreamTrack* t = track->AsVideoStreamTrack()) {
+      nsRefPtr<VideoTrack> videoTrack = CreateVideoTrack(t);
+      VideoTracks()->AddTrack(videoTrack);
+      firstEnabledVideo = (t->Enabled() && firstEnabledVideo < 0)
+                          ? (VideoTracks()->Length() - 1)
+                          : firstEnabledVideo;
+    }
+  }
+
+  if (VideoTracks()->Length() > 0) {
+    // If media resource does not indicate a particular set of video tracks to
+    // enable, the one that is listed first in the element's videoTracks object
+    // must be selected.
+    int index = firstEnabledVideo >= 0 ? firstEnabledVideo : 0;
+    (*VideoTracks())[index]->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
+  }
+}
+
+void
+HTMLMediaElement::NotifyMediaStreamTrackAdded(const nsRefPtr<MediaStreamTrack>& aTrack)
+{
+  MOZ_ASSERT(aTrack);
+
+#ifdef DEBUG
+  nsString id;
+  aTrack->GetId(id);
+
+  LOG(LogLevel::Debug, ("%p, Adding MediaTrack with id %s",
+                        this, NS_ConvertUTF16toUTF8(id).get()));
+#endif
+
+  if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) {
+    nsRefPtr<AudioTrack> audioTrack = CreateAudioTrack(t);
+    AudioTracks()->AddTrack(audioTrack);
+  } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) {
+    nsRefPtr<VideoTrack> videoTrack = CreateVideoTrack(t);
+    VideoTracks()->AddTrack(videoTrack);
+  }
+}
+
+void
+HTMLMediaElement::NotifyMediaStreamTrackRemoved(const nsRefPtr<MediaStreamTrack>& aTrack)
+{
+  MOZ_ASSERT(aTrack);
+
+  nsAutoString id;
+  aTrack->GetId(id);
+
+  LOG(LogLevel::Debug, ("%p, Removing MediaTrack with id %s",
+                        this, NS_ConvertUTF16toUTF8(id).get()));
+
+  if (MediaTrack* t = AudioTracks()->GetTrackById(id)) {
+    AudioTracks()->RemoveTrack(t);
+  } else if (MediaTrack* t = VideoTracks()->GetTrackById(id)) {
+    VideoTracks()->RemoveTrack(t);
+  } else {
+    // XXX Uncomment this when DOMMediaStream doesn't call NotifyTrackRemoved
+    // multiple times for the same track, i.e., when it implements the
+    // "addtrack" and "removetrack" events.
+    // NS_ASSERTION(false, "MediaStreamTrack ended but did not exist in track lists");
+    return;
+  }
+}
+
+
 void HTMLMediaElement::ProcessMediaFragmentURI()
 {
   nsMediaFragmentURIParser parser(mLoadingSrc);
 
   if (mDecoder && parser.HasEndTime()) {
     mFragmentEnd = parser.GetEndTime();
   }
 
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -666,16 +666,17 @@ public:
   float ComputedVolume() const;
   bool ComputedMuted() const;
 
 protected:
   virtual ~HTMLMediaElement();
 
   class MediaLoadListener;
   class MediaStreamTracksAvailableCallback;
+  class MediaStreamTrackListener;
   class StreamListener;
   class StreamSizeListener;
 
   MediaDecoderOwner::NextFrameStatus NextFrameStatus();
   void SetDecoder(MediaDecoder* aDecoder) {
     MOZ_ASSERT(aDecoder); // Use ShutdownDecoder() to clear.
     mDecoder = aDecoder;
   }
@@ -756,16 +757,35 @@ protected:
   void EndSrcMediaStreamPlayback();
   /**
    * Ensure we're playing mSrcStream if and only if we're not paused.
    */
   enum { REMOVING_SRC_STREAM = 0x1 };
   void UpdateSrcMediaStreamPlaying(uint32_t aFlags = 0);
 
   /**
+   * If loading and playing a MediaStream, for each MediaStreamTrack in the
+   * MediaStream, create a corresponding AudioTrack or VideoTrack during the
+   * phase of resource fetching.
+   */
+  void ConstructMediaTracks();
+
+  /**
+   * Called by our DOMMediaStream::TrackListener when a new MediaStreamTrack has
+   * been added to the playback stream of |mSrcStream|.
+   */
+  void NotifyMediaStreamTrackAdded(const nsRefPtr<MediaStreamTrack>& aTrack);
+
+  /**
+   * Called by our DOMMediaStream::TrackListener when a MediaStreamTrack in
+   * |mSrcStream|'s playback stream has ended.
+   */
+  void NotifyMediaStreamTrackRemoved(const nsRefPtr<MediaStreamTrack>& aTrack);
+
+  /**
    * Returns an nsDOMMediaStream containing the played contents of this
    * element. When aFinishWhenEnded is true, when this element ends playback
    * we will finish the stream and not play any more into it.
    * When aFinishWhenEnded is false, ending playback does not finish the stream.
    * The stream will never finish.
    */
   already_AddRefed<DOMMediaStream> CaptureStreamInternal(bool aFinishWhenEnded,
                                                          MediaStreamGraph* aGraph = nullptr);
@@ -1424,16 +1444,18 @@ protected:
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 
   nsRefPtr<TextTrackManager> mTextTrackManager;
 
   nsRefPtr<AudioTrackList> mAudioTrackList;
 
   nsRefPtr<VideoTrackList> mVideoTrackList;
 
+  nsRefPtr<MediaStreamTrackListener> mMediaStreamTrackListener;
+
   enum ElementInTreeState {
     // The MediaElement is not in the DOM tree now.
     ELEMENT_NOT_INTREE,
     // The MediaElement is in the DOM tree now.
     ELEMENT_INTREE,
     // The MediaElement is not in the DOM tree now but had been binded to the
     // tree before.
     ELEMENT_NOT_INTREE_HAD_INTREE
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -493,17 +493,17 @@ DOMMediaStream::AddTrack(MediaStreamTrac
   CombineWithPrincipal(addedDOMStream->mPrincipal);
 
   // Hook up the underlying track with our underlying playback stream.
   nsRefPtr<MediaInputPort> inputPort =
     GetPlaybackStream()->AllocateInputPort(owningStream, aTrack.GetTrackID());
   nsRefPtr<TrackPort> trackPort =
     new TrackPort(inputPort, &aTrack, TrackPort::InputPortOwnership::OWNED);
   mTracks.AppendElement(trackPort.forget());
-  NotifyMediaStreamTrackCreated(&aTrack);
+  NotifyTrackAdded(&aTrack);
 
   LOG(LogLevel::Debug, ("DOMMediaStream %p Added track %p", this, &aTrack));
 }
 
 void
 DOMMediaStream::RemoveTrack(MediaStreamTrack& aTrack)
 {
   LOG(LogLevel::Info, ("DOMMediaStream %p Removing track %p (from stream %p with ID %d)",
@@ -724,17 +724,17 @@ DOMMediaStream::CreateOwnDOMTrack(TrackI
   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);
+  NotifyTrackAdded(track);
   return track;
 }
 
 MediaStreamTrack*
 DOMMediaStream::FindOwnedDOMTrack(MediaStream* aOwningStream, TrackID aTrackID) const
 {
   if (aOwningStream != mOwnedStream) {
     return nullptr;
@@ -783,17 +783,17 @@ DOMMediaStream::FindPlaybackTrackPort(co
 
 void
 DOMMediaStream::NotifyMediaStreamGraphShutdown()
 {
   // No more tracks will ever be added, so just clear these callbacks now
   // to prevent leaks.
   mNotifiedOfMediaStreamGraphShutdown = true;
   mRunOnTracksAvailable.Clear();
-
+  mTrackListeners.Clear();
   mConsumersToKeepAlive.Clear();
 }
 
 void
 DOMMediaStream::NotifyStreamFinished()
 {
   MOZ_ASSERT(IsFinished());
   mConsumersToKeepAlive.Clear();
@@ -807,17 +807,17 @@ DOMMediaStream::OnTracksAvailable(OnTrac
     delete aRunnable;
     return;
   }
   mRunOnTracksAvailable.AppendElement(aRunnable);
   CheckTracksAvailable();
 }
 
 void
-DOMMediaStream::TracksCreated()
+DOMMediaStream::NotifyTracksCreated()
 {
   mTracksCreated = true;
   CheckTracksAvailable();
 }
 
 void
 DOMMediaStream::CheckTracksAvailable()
 {
@@ -828,121 +828,66 @@ DOMMediaStream::CheckTracksAvailable()
   callbacks.SwapElements(mRunOnTracksAvailable);
 
   for (uint32_t i = 0; i < callbacks.Length(); ++i) {
     callbacks[i]->NotifyTracksAvailable(this);
   }
 }
 
 void
+DOMMediaStream::RegisterTrackListener(TrackListener* aListener)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mNotifiedOfMediaStreamGraphShutdown) {
+    // No more tracks will ever be added, so just do nothing.
+    return;
+  }
+  mTrackListeners.AppendElement(aListener);
+}
+
+void
+DOMMediaStream::UnregisterTrackListener(TrackListener* aListener)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mTrackListeners.RemoveElement(aListener);
+}
+
+void
+DOMMediaStream::NotifyTrackAdded(
+    const nsRefPtr<MediaStreamTrack>& aTrack)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
+    const nsRefPtr<TrackListener>& listener = mTrackListeners[i];
+    listener->NotifyTrackAdded(aTrack);
+  }
+}
+
+void
+DOMMediaStream::NotifyTrackRemoved(
+    const nsRefPtr<MediaStreamTrack>& aTrack)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
+    const nsRefPtr<TrackListener>& listener = mTrackListeners[i];
+    listener->NotifyTrackRemoved(aTrack);
+  }
+}
+
+void
 DOMMediaStream::CreateAndAddPlaybackStreamListener(MediaStream* aStream)
 {
   MOZ_ASSERT(GetCameraStream(), "I'm a hack. Only DOMCameraControl may use me.");
   mPlaybackListener = new PlaybackStreamListener(this);
   aStream->AddListener(mPlaybackListener);
 }
 
-already_AddRefed<AudioTrack>
-DOMMediaStream::CreateAudioTrack(AudioStreamTrack* aStreamTrack)
-{
-  nsAutoString id;
-  nsAutoString label;
-  aStreamTrack->GetId(id);
-  aStreamTrack->GetLabel(label);
-
-  return MediaTrackList::CreateAudioTrack(id, NS_LITERAL_STRING("main"),
-                                          label, EmptyString(),
-                                          aStreamTrack->Enabled());
-}
-
-already_AddRefed<VideoTrack>
-DOMMediaStream::CreateVideoTrack(VideoStreamTrack* aStreamTrack)
-{
-  nsAutoString id;
-  nsAutoString label;
-  aStreamTrack->GetId(id);
-  aStreamTrack->GetLabel(label);
-
-  return MediaTrackList::CreateVideoTrack(id, NS_LITERAL_STRING("main"),
-                                          label, EmptyString());
-}
-
-void
-DOMMediaStream::ConstructMediaTracks(AudioTrackList* aAudioTrackList,
-                                     VideoTrackList* aVideoTrackList)
-{
-  MediaTrackListListener audioListener(aAudioTrackList);
-  mMediaTrackListListeners.AppendElement(audioListener);
-  MediaTrackListListener videoListener(aVideoTrackList);
-  mMediaTrackListListeners.AppendElement(videoListener);
-
-  int firstEnabledVideo = -1;
-  for (const nsRefPtr<TrackPort>& info : mTracks) {
-    if (AudioStreamTrack* t = info->GetTrack()->AsAudioStreamTrack()) {
-      nsRefPtr<AudioTrack> track = CreateAudioTrack(t);
-      aAudioTrackList->AddTrack(track);
-    } else if (VideoStreamTrack* t = info->GetTrack()->AsVideoStreamTrack()) {
-      nsRefPtr<VideoTrack> track = CreateVideoTrack(t);
-      aVideoTrackList->AddTrack(track);
-      firstEnabledVideo = (t->Enabled() && firstEnabledVideo < 0)
-                          ? (aVideoTrackList->Length() - 1)
-                          : firstEnabledVideo;
-    }
-  }
-
-  if (aVideoTrackList->Length() > 0) {
-    // If media resource does not indicate a particular set of video tracks to
-    // enable, the one that is listed first in the element's videoTracks object
-    // must be selected.
-    int index = firstEnabledVideo >= 0 ? firstEnabledVideo : 0;
-    (*aVideoTrackList)[index]->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
-  }
-}
-
-void
-DOMMediaStream::DisconnectTrackListListeners(const AudioTrackList* aAudioTrackList,
-                                             const VideoTrackList* aVideoTrackList)
-{
-  for (auto i = mMediaTrackListListeners.Length(); i > 0; ) { // unsigned!
-    --i; // 0 ... Length()-1 range
-    if (mMediaTrackListListeners[i].mMediaTrackList == aAudioTrackList ||
-        mMediaTrackListListeners[i].mMediaTrackList == aVideoTrackList) {
-      mMediaTrackListListeners.RemoveElementAt(i);
-    }
-  }
-}
-
-void
-DOMMediaStream::NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack)
-{
-  MOZ_ASSERT(aTrack);
-
-  for (uint32_t i = 0; i < mMediaTrackListListeners.Length(); ++i) {
-    if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) {
-      nsRefPtr<AudioTrack> track = CreateAudioTrack(t);
-      mMediaTrackListListeners[i].NotifyMediaTrackCreated(track);
-    } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) {
-      nsRefPtr<VideoTrack> track = CreateVideoTrack(t);
-      mMediaTrackListListeners[i].NotifyMediaTrackCreated(track);
-    }
-  }
-}
-
-void
-DOMMediaStream::NotifyMediaStreamTrackEnded(MediaStreamTrack* aTrack)
-{
-  MOZ_ASSERT(aTrack);
-
-  nsAutoString id;
-  aTrack->GetId(id);
-  for (uint32_t i = 0; i < mMediaTrackListListeners.Length(); ++i) {
-    mMediaTrackListListeners[i].NotifyMediaTrackEnded(id);
-  }
-}
-
 DOMLocalMediaStream::~DOMLocalMediaStream()
 {
   if (mInputStream) {
     // Make sure Listeners of this stream know it's going away
     StopImpl();
   }
 }
 
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -177,21 +177,41 @@ class DOMMediaStream : public DOMEventTa
   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;
-  typedef dom::MediaTrackListListener MediaTrackListListener;
 
 public:
   typedef dom::MediaTrackConstraints MediaTrackConstraints;
-  typedef uint8_t TrackTypeHints;
+
+  class TrackListener {
+    NS_INLINE_DECL_REFCOUNTING(TrackListener)
+
+  public:
+    /**
+     * Called when the DOMMediaStream has a new track added, either by
+     * JS (addTrack()) or the source creating one.
+     */
+    virtual void
+    NotifyTrackAdded(const nsRefPtr<MediaStreamTrack>& aTrack) {};
+
+    /**
+     * Called when the DOMMediaStream removes a track, either by
+     * JS (removeTrack()) or the source ending it.
+     */
+    virtual void
+    NotifyTrackRemoved(const nsRefPtr<MediaStreamTrack>& aTrack) {};
+
+  protected:
+    virtual ~TrackListener() {}
+  };
 
   DOMMediaStream();
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMMediaStream,
                                            DOMEventTargetHelper)
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOMMEDIASTREAM_IID)
@@ -389,50 +409,39 @@ public:
    */
   void AddConsumerToKeepAlive(nsISupports* aConsumer)
   {
     if (!IsFinished() && !mNotifiedOfMediaStreamGraphShutdown) {
       mConsumersToKeepAlive.AppendElement(aConsumer);
     }
   }
 
-  /**
-   * If loading and playing a MediaStream in a media element, for each
-   * MediaStreamTrack in the MediaStream, create a corresponding AudioTrack or
-   * VideoTrack during the phase of resource fetching.
-   */
-  void ConstructMediaTracks(AudioTrackList* aAudioTrackList,
-                            VideoTrackList* aVideoTrackList);
-
-  /**
-   * MUST call this before the AudioTrackList or VideoTrackList go away
-   */
-  void DisconnectTrackListListeners(const AudioTrackList* aAudioTrackList,
-                                    const VideoTrackList* aVideoTrackList);
-
-  virtual void NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack);
-
-  virtual void NotifyMediaStreamTrackEnded(MediaStreamTrack* aTrack);
+  void RegisterTrackListener(TrackListener* aListener);
+  void UnregisterTrackListener(TrackListener* aListener);
 
 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, MediaStreamGraph* aGraph);
-  already_AddRefed<AudioTrack> CreateAudioTrack(AudioStreamTrack* aStreamTrack);
-  already_AddRefed<VideoTrack> CreateVideoTrack(VideoStreamTrack* aStreamTrack);
+
+  void CheckTracksAvailable();
 
   // Called when MediaStreamGraph has finished an iteration where tracks were
   // created.
-  void TracksCreated();
+  void NotifyTracksCreated();
 
-  void CheckTracksAvailable();
+  // Dispatches NotifyTrackAdded() to all registered track listeners.
+  void NotifyTrackAdded(const nsRefPtr<MediaStreamTrack>& aTrack);
+
+  // Dispatches NotifyTrackRemoved() to all registered track listeners.
+  void NotifyTrackRemoved(const nsRefPtr<MediaStreamTrack>& aTrack);
 
   class OwnedStreamListener;
   friend class OwnedStreamListener;
 
   class PlaybackStreamListener;
   friend class PlaybackStreamListener;
 
   // XXX Bug 1124630. Remove with CameraPreviewMediaStream.
@@ -483,19 +492,18 @@ protected:
 
   nsString mID;
 
   // Keep these alive until the stream finishes
   nsTArray<nsCOMPtr<nsISupports> > mConsumersToKeepAlive;
 
   bool mNotifiedOfMediaStreamGraphShutdown;
 
-  // Send notifications to AudioTrackList or VideoTrackList, if this MediaStream
-  // is consumed by a HTMLMediaElement.
-  nsTArray<MediaTrackListListener> mMediaTrackListListeners;
+  // The track listeners subscribe to changes in this stream's track set.
+  nsTArray<nsRefPtr<TrackListener>> mTrackListeners;
 
 private:
   void NotifyPrincipalChanged();
 
   // Principal identifying who may access the contents of this stream.
   // If null, this stream can be used by anyone because it has no content yet.
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsTArray<PrincipalChangeObserver*> mPrincipalChangeObservers;
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -56,16 +56,17 @@ public:
   void GetId(nsAString& aID) const;
   void GetLabel(nsAString& aLabel) { aLabel.Truncate(); }
   bool Enabled() { return mEnabled; }
   void SetEnabled(bool aEnabled);
   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; }
 
   // 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:
--- a/dom/media/MediaTrackList.cpp
+++ b/dom/media/MediaTrackList.cpp
@@ -11,43 +11,16 @@
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/TrackEvent.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
-void
-MediaTrackListListener::NotifyMediaTrackCreated(MediaTrack* aTrack)
-{
-  if (!mMediaTrackList && !aTrack) {
-    return;
-  }
-
-  if (aTrack->AsAudioTrack() && mMediaTrackList->AsAudioTrackList()) {
-    mMediaTrackList->AddTrack(aTrack);
-  } else if (aTrack->AsVideoTrack() && mMediaTrackList->AsVideoTrackList()) {
-    mMediaTrackList->AddTrack(aTrack);
-  }
-}
-
-void
-MediaTrackListListener::NotifyMediaTrackEnded(const nsAString& aId)
-{
-  if (!mMediaTrackList) {
-    return;
-  }
-
-  const nsRefPtr<MediaTrack> track = mMediaTrackList->GetTrackById(aId);
-  if (track) {
-    mMediaTrackList->RemoveTrack(track);
-  }
-}
-
 MediaTrackList::MediaTrackList(nsPIDOMWindow* aOwnerWindow,
                                HTMLMediaElement* aMediaElement)
   : DOMEventTargetHelper(aOwnerWindow)
   , mMediaElement(aMediaElement)
 {
 }
 
 MediaTrackList::~MediaTrackList()
--- a/dom/media/MediaTrackList.h
+++ b/dom/media/MediaTrackList.h
@@ -15,51 +15,16 @@ class DOMMediaStream;
 namespace dom {
 
 class HTMLMediaElement;
 class MediaTrack;
 class AudioTrackList;
 class VideoTrackList;
 class AudioTrack;
 class VideoTrack;
-class MediaTrackList;
-
-/**
- * This is for the media resource to notify its audio track and video track,
- * when a media-resource-specific track has ended, or whether it has enabled or
- * not. All notification methods are called from the main thread.
- */
-class MediaTrackListListener
-{
-public:
-  friend class mozilla::DOMMediaStream;
-
-  explicit MediaTrackListListener(MediaTrackList* aMediaTrackList)
-    : mMediaTrackList(aMediaTrackList) {};
-
-  ~MediaTrackListListener()
-  {
-    mMediaTrackList = nullptr;
-  };
-
-  // Notify mMediaTrackList that a track has created by the media resource,
-  // and this corresponding MediaTrack object should be added into
-  // mMediaTrackList, and fires a addtrack event.
-  void NotifyMediaTrackCreated(MediaTrack* aTrack);
-
-  // Notify mMediaTrackList that a track has ended by the media resource,
-  // and this corresponding MediaTrack object should be removed from
-  // mMediaTrackList, and fires a removetrack event.
-  void NotifyMediaTrackEnded(const nsAString& aId);
-
-protected:
-  // A weak reference to a MediaTrackList object, its lifetime managed by its
-  // owner.
-  MediaTrackList* mMediaTrackList;
-};
 
 /**
  * Base class of AudioTrackList and VideoTrackList. The AudioTrackList and
  * VideoTrackList objects represent a dynamic list of zero or more audio and
  * video tracks respectively.
  *
  * When a media element is to forget its media-resource-specific tracks, its
  * audio track list and video track list will be emptied.
@@ -115,17 +80,16 @@ public:
   {
     return mTracks.Length();
   }
 
   IMPL_EVENT_HANDLER(change)
   IMPL_EVENT_HANDLER(addtrack)
   IMPL_EVENT_HANDLER(removetrack)
 
-  friend class MediaTrackListListener;
   friend class AudioTrack;
   friend class VideoTrack;
 
 protected:
   virtual ~MediaTrackList();
 
   void CreateAndDispatchTrackEventRunner(MediaTrack* aTrack,
                                          const nsAString& aEventName);