Bug 1266646 - Change HTMLMediaElement::StreamSizeListerner to inherit MediaStreamTrackDirectListener. r=pehrsons
authorctai <ctai@mozilla.com>
Tue, 10 May 2016 17:02:15 +0800
changeset 346047 49295086e189276fdab180f5e907538d929c6f7f
parent 346046 6062a5e09ab773085a16cd0cd91f0cb0c352aa9a
child 346048 fc2c0cb9c6a822ca684742c79a88eab3c1c301ac
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspehrsons
bugs1266646
milestone50.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 1266646 - Change HTMLMediaElement::StreamSizeListerner to inherit MediaStreamTrackDirectListener. r=pehrsons MozReview-Commit-ID: HnNv9BnlbDy
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/MediaStreamGraph.cpp
dom/media/MediaStreamListener.h
dom/media/MediaStreamTrack.cpp
dom/media/MediaStreamTrack.h
dom/media/MediaTrackList.cpp
dom/media/MediaTrackList.h
dom/media/VideoTrack.cpp
dom/media/VideoTrack.h
dom/media/VideoTrackList.cpp
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -270,16 +270,66 @@ public:
                                                 mSource,
                                                 NS_LITERAL_STRING("error"),
                                                 false,
                                                 false);
   }
 };
 
 /**
+ * This listener observes the first video frame to arrive with a non-empty size,
+ * and calls HTMLMediaElement::ReceivedMediaStreamInitialSize() with that size.
+ */
+class HTMLMediaElement::StreamSizeListener : public DirectMediaStreamTrackListener {
+public:
+  explicit StreamSizeListener(HTMLMediaElement* aElement) :
+    mElement(aElement),
+    mInitialSizeFound(false)
+  {}
+  void Forget() { mElement = nullptr; }
+
+  void ReceivedSize(gfx::IntSize aSize)
+  {
+    if (!mElement) {
+      return;
+    }
+    RefPtr<HTMLMediaElement> deathGrip = mElement;
+    mElement->UpdateInitialMediaSize(aSize);
+  }
+
+  void NotifyRealtimeTrackData(MediaStreamGraph* aGraph,
+                               StreamTime aTrackOffset,
+                               const MediaSegment& aMedia) override
+  {
+    if (mInitialSizeFound || aMedia.GetType() != MediaSegment::VIDEO) {
+      return;
+    }
+    const VideoSegment& video = static_cast<const VideoSegment&>(aMedia);
+    for (VideoSegment::ConstChunkIterator c(video); !c.IsEnded(); c.Next()) {
+      if (c->mFrame.GetIntrinsicSize() != gfx::IntSize(0,0)) {
+        mInitialSizeFound = true;
+        nsCOMPtr<nsIRunnable> event =
+          NewRunnableMethod<gfx::IntSize>(
+              this, &StreamSizeListener::ReceivedSize,
+              c->mFrame.GetIntrinsicSize());
+        aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+        return;
+      }
+    }
+  }
+
+private:
+  // These fields may only be accessed on the main thread
+  HTMLMediaElement* mElement;
+
+  // These fields may only be accessed on the MSG thread
+  bool mInitialSizeFound;
+};
+
+/**
  * There is a reference cycle involving this class: MediaLoadListener
  * holds a reference to the HTMLMediaElement, which holds a reference
  * to an nsIChannel, which holds a reference to this listener.
  * We break the reference cycle in OnStartRequest by clearing mElement.
  */
 class HTMLMediaElement::MediaLoadListener final : public nsIStreamListener,
                                                   public nsIChannelEventSink,
                                                   public nsIInterfaceRequestor,
@@ -652,16 +702,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
 #ifdef MOZ_EME
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
 #endif
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
   if (tmp->mSrcStream) {
     // Need to EndMediaStreamPlayback to clear mSrcStream and make sure everything
     // gets unhooked correctly.
     tmp->EndSrcMediaStreamPlayback();
   }
@@ -678,16 +729,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
 #ifdef MOZ_EME
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
 #endif
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement)
   NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLMediaElement)
   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
 
 // nsIDOMHTMLMediaElement
@@ -876,16 +928,24 @@ void HTMLMediaElement::AbortExistingLoad
 
   if (mChannelLoader) {
     mChannelLoader->Cancel();
     mChannelLoader = nullptr;
   }
 
   bool fireTimeUpdate = false;
 
+  // We need to remove StreamSizeListener before VideoTracks get emptied.
+  if (mMediaStreamSizeListener) {
+    mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
+    mSelectedVideoStreamTrack = nullptr;
+    mMediaStreamSizeListener->Forget();
+    mMediaStreamSizeListener = nullptr;
+  }
+
   // When aborting the existing loads, empty the objects in audio track list and
   // video track list, no events (in particular, no removetrack events) are
   // fired as part of this. Ending MediaStream sends track ended notifications,
   // so we empty the track lists prior.
   AudioTracks()->EmptyTracks();
   VideoTracks()->EmptyTracks();
 
   if (mDecoder) {
@@ -3368,69 +3428,16 @@ private:
   bool mBlocked;
   bool mFinished;
 
   // mMutex protects the fields below; they can be accessed on any thread
   Mutex mMutex;
   bool mPendingNotifyOutput;
 };
 
-/**
- * This listener observes the first video frame to arrive with a non-empty size,
- * and calls HTMLMediaElement::ReceivedMediaStreamInitialSize() with that size.
- */
-class HTMLMediaElement::StreamSizeListener : public MediaStreamListener {
-public:
-  explicit StreamSizeListener(HTMLMediaElement* aElement) :
-    mElement(aElement),
-    mInitialSizeFound(false)
-  {}
-  void Forget() { mElement = nullptr; }
-
-  void ReceivedSize(gfx::IntSize aSize)
-  {
-    if (!mElement) {
-      return;
-    }
-    RefPtr<HTMLMediaElement> deathGrip = mElement;
-    mElement->UpdateInitialMediaSize(aSize);
-  }
-
-  void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
-                                StreamTime aTrackOffset,
-                                TrackEventCommand aTrackEvents,
-                                const MediaSegment& aQueuedMedia,
-                                MediaStream* aInputStream,
-                                TrackID aInputTrackID) override
-  {
-    if (mInitialSizeFound || aQueuedMedia.GetType() != MediaSegment::VIDEO) {
-      return;
-    }
-    const VideoSegment& video = static_cast<const VideoSegment&>(aQueuedMedia);
-    for (VideoSegment::ConstChunkIterator c(video); !c.IsEnded(); c.Next()) {
-      if (c->mFrame.GetIntrinsicSize() != gfx::IntSize(0,0)) {
-        mInitialSizeFound = true;
-        nsCOMPtr<nsIRunnable> event =
-          NewRunnableMethod<gfx::IntSize>(
-              this, &StreamSizeListener::ReceivedSize,
-              c->mFrame.GetIntrinsicSize());
-        aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
-        return;
-      }
-    }
-  }
-
-private:
-  // These fields may only be accessed on the main thread
-  HTMLMediaElement* mElement;
-
-  // These fields may only be accessed on the MSG thread
-  bool mInitialSizeFound;
-};
-
 class HTMLMediaElement::MediaStreamTracksAvailableCallback:
   public OnTracksAvailableCallback
 {
 public:
   explicit MediaStreamTracksAvailableCallback(HTMLMediaElement* aElement):
       OnTracksAvailableCallback(), mElement(aElement)
     {}
   virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
@@ -3539,19 +3546,16 @@ void HTMLMediaElement::SetupSrcMediaStre
   nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
   if (!window) {
     return;
   }
 
   RefPtr<MediaStream> stream = GetSrcMediaStream();
   if (stream) {
     stream->SetAudioChannelType(mAudioChannel);
-
-    mMediaStreamSizeListener = new StreamSizeListener(this);
-    stream->AddListener(mMediaStreamSizeListener);
   }
 
   UpdateSrcMediaStreamPlaying();
 
   // 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...
   ConstructMediaTracks();
@@ -3572,20 +3576,18 @@ void HTMLMediaElement::SetupSrcMediaStre
 
 void HTMLMediaElement::EndSrcMediaStreamPlayback()
 {
   MOZ_ASSERT(mSrcStream);
 
   UpdateSrcMediaStreamPlaying(REMOVING_SRC_STREAM);
 
   if (mMediaStreamSizeListener) {
-    RefPtr<MediaStream> stream = GetSrcMediaStream();
-    if (stream) {
-      stream->RemoveListener(mMediaStreamSizeListener);
-    }
+    mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
+    mSelectedVideoStreamTrack = nullptr;
     mMediaStreamSizeListener->Forget();
     mMediaStreamSizeListener = nullptr;
   }
 
   mSrcStream->UnregisterTrackListener(mMediaStreamTrackListener);
   mMediaStreamTrackListener = nullptr;
 
   mSrcStream->RemovePrincipalChangeObserver(this);
@@ -3611,17 +3613,18 @@ 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());
+                                          label, EmptyString(),
+                                          aStreamTrack);
 }
 
 void HTMLMediaElement::ConstructMediaTracks()
 {
   nsTArray<RefPtr<MediaStreamTrack>> tracks;
   mSrcStream->GetTracks(tracks);
 
   int firstEnabledVideo = -1;
@@ -3643,16 +3646,21 @@ void HTMLMediaElement::ConstructMediaTra
   }
 
   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);
+    VideoTrack* track = (*VideoTracks())[index];
+    VideoStreamTrack* streamTrack = track->GetVideoStreamTrack();
+    mMediaStreamSizeListener = new StreamSizeListener(this);
+    streamTrack->AddDirectListener(mMediaStreamSizeListener);
+    mSelectedVideoStreamTrack = streamTrack;
   }
 }
 
 void
 HTMLMediaElement::NotifyMediaStreamTrackAdded(const RefPtr<MediaStreamTrack>& aTrack)
 {
   MOZ_ASSERT(aTrack);
 
@@ -3663,18 +3671,30 @@ HTMLMediaElement::NotifyMediaStreamTrack
   LOG(LogLevel::Debug, ("%p, Adding MediaTrack with id %s",
                         this, NS_ConvertUTF16toUTF8(id).get()));
 #endif
 
   if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) {
     RefPtr<AudioTrack> audioTrack = CreateAudioTrack(t);
     AudioTracks()->AddTrack(audioTrack);
   } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) {
+    // TODO: Fix this per the spec on bug 1273443.
+    int32_t selectedIndex = VideoTracks()->SelectedIndex();
     RefPtr<VideoTrack> videoTrack = CreateVideoTrack(t);
     VideoTracks()->AddTrack(videoTrack);
+    // New MediaStreamTrack added, set the new added video track as selected
+    // video track when there is no selected track.
+    if (selectedIndex == -1) {
+      MOZ_ASSERT(!mSelectedVideoStreamTrack);
+      videoTrack->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
+      mMediaStreamSizeListener = new StreamSizeListener(this);
+      t->AddDirectListener(mMediaStreamSizeListener);
+      mSelectedVideoStreamTrack = t;
+    }
+
   }
 }
 
 void
 HTMLMediaElement::NotifyMediaStreamTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack)
 {
   MOZ_ASSERT(aTrack);
 
@@ -3683,16 +3703,58 @@ HTMLMediaElement::NotifyMediaStreamTrack
 
   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);
+    // TODO: Fix this per the spec on bug 1273443.
+    // If the removed media stream track is selected video track and there are
+    // still video tracks, change the selected video track to the first
+    // remaining track.
+    if (aTrack == mSelectedVideoStreamTrack) {
+      // The mMediaStreamSizeListener might already reset to nullptr.
+      if (mMediaStreamSizeListener) {
+        mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
+      }
+      mSelectedVideoStreamTrack = nullptr;
+      MOZ_ASSERT(mSrcStream);
+      nsTArray<RefPtr<VideoStreamTrack>> tracks;
+      mSrcStream->GetVideoTracks(tracks);
+
+      for (const RefPtr<VideoStreamTrack>& track : tracks) {
+        if (track->Ended()) {
+          continue;
+        }
+        if (!track->Enabled()) {
+          continue;
+        }
+
+        nsAutoString trackId;
+        track->GetId(trackId);
+        MediaTrack* videoTrack = VideoTracks()->GetTrackById(trackId);
+        MOZ_ASSERT(videoTrack);
+
+        videoTrack->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
+        if (mMediaStreamSizeListener) {
+          track->AddDirectListener(mMediaStreamSizeListener);
+        }
+        mSelectedVideoStreamTrack = track;
+        return;
+      }
+
+      // There is no enabled video track existing, clean the
+      // mMediaStreamSizeListener.
+      if (mMediaStreamSizeListener) {
+        mMediaStreamSizeListener->Forget();
+        mMediaStreamSizeListener = nullptr;
+      }
+    }
   } else {
     // XXX (bug 1208328) 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;
   }
 }
@@ -4601,20 +4663,17 @@ void HTMLMediaElement::UpdateInitialMedi
 {
   if (!mMediaInfo.HasVideo()) {
     UpdateMediaSize(aSize);
   }
 
   if (!mMediaStreamSizeListener) {
     return;
   }
-  RefPtr<MediaStream> stream = GetSrcMediaStream();
-  if (stream) {
-    stream->RemoveListener(mMediaStreamSizeListener);
-  }
+  mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
   mMediaStreamSizeListener->Forget();
   mMediaStreamSizeListener = nullptr;
 }
 
 void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement, bool aSuspendEvents)
 {
   LOG(LogLevel::Debug, ("%p SuspendOrResumeElement(pause=%d, suspendEvents=%d) hidden=%d",
       this, aPauseElement, aSuspendEvents, OwnerDoc()->Hidden()));
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1239,16 +1239,18 @@ protected:
   nsTArray<OutputMediaStream> mOutputStreams;
 
   // Holds a reference to the MediaStreamListener attached to mSrcStream's
   // playback stream.
   RefPtr<StreamListener> mMediaStreamListener;
   // Holds a reference to the size-getting MediaStreamListener attached to
   // mSrcStream.
   RefPtr<StreamSizeListener> mMediaStreamSizeListener;
+  // The selected video stream track which contained mMediaStreamSizeListener.
+  RefPtr<VideoStreamTrack> mSelectedVideoStreamTrack;
 
   const RefPtr<ShutdownObserver> mShutdownObserver;
 
   // Holds a reference to the MediaSource, if any, referenced by the src
   // attribute on the media element.
   RefPtr<MediaSource> mSrcMediaSource;
 
   // Holds a reference to the MediaSource supplying data for playback.  This
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -2939,43 +2939,48 @@ SourceMediaStream::RemoveDirectListener(
 void
 SourceMediaStream::AddDirectTrackListenerImpl(already_AddRefed<DirectMediaStreamTrackListener> aListener,
                                               TrackID aTrackID)
 {
   MOZ_ASSERT(IsTrackIDExplicit(aTrackID));
   TrackData* data;
   bool found;
   bool isAudio;
+  bool isVideo;
   RefPtr<DirectMediaStreamTrackListener> listener = aListener;
   STREAM_LOG(LogLevel::Debug, ("Adding direct track listener %p bound to track %d to source stream %p",
              listener.get(), aTrackID, this));
+
   {
     MutexAutoLock lock(mMutex);
     data = FindDataForTrack(aTrackID);
     found = !!data;
-    isAudio = found && data->mData->GetType() == MediaSegment::AUDIO;
-    if (found && isAudio) {
+    if (found) {
+      isAudio = data->mData->GetType() == MediaSegment::AUDIO;
+      isVideo = data->mData->GetType() == MediaSegment::VIDEO;
+    }
+    if (found && (isAudio || isVideo)) {
       TrackBound<DirectMediaStreamTrackListener>* sourceListener =
         mDirectTrackListeners.AppendElement();
       sourceListener->mListener = listener;
       sourceListener->mTrackID = aTrackID;
     }
   }
   if (!found) {
     STREAM_LOG(LogLevel::Warning, ("Couldn't find source track for direct track listener %p",
                                    listener.get()));
     listener->NotifyDirectListenerInstalled(
       DirectMediaStreamTrackListener::InstallationResult::TRACK_NOT_FOUND_AT_SOURCE);
     return;
   }
-  if (!isAudio) {
-    STREAM_LOG(LogLevel::Warning, ("Source track for direct track listener %p is not audio",
+  if (!isAudio && !isVideo) {
+    STREAM_LOG(LogLevel::Warning, ("Source track for direct track listener %p is unknown",
                                    listener.get()));
-    listener->NotifyDirectListenerInstalled(
-      DirectMediaStreamTrackListener::InstallationResult::TRACK_TYPE_NOT_SUPPORTED);
+    // It is not a video or audio track.
+    MOZ_ASSERT(true);
     return;
   }
   STREAM_LOG(LogLevel::Debug, ("Added direct track listener %p", listener.get()));
   listener->NotifyDirectListenerInstalled(
     DirectMediaStreamTrackListener::InstallationResult::SUCCESS);
 }
 
 void
--- a/dom/media/MediaStreamListener.h
+++ b/dom/media/MediaStreamListener.h
@@ -233,19 +233,16 @@ public:
   /**
    * When a direct listener is processed for installation by the
    * MediaStreamGraph it will be notified with whether the installation was
    * successful or not. The results of this installation are the following:
    * TRACK_NOT_FOUND_AT_SOURCE
    *    We found the source stream of media data for this track, but the track
    *    didn't exist. This should only happen if you try to install the listener
    *    directly to a SourceMediaStream that doesn't contain the given TrackID.
-   * TRACK_TYPE_NOT_SUPPORTED
-   *    This is the failure when you install the listener to a
-   *    non-(audio or video) track.
    * STREAM_NOT_SUPPORTED
    *    While looking for the data source of this track, we found a MediaStream
    *    that is not a SourceMediaStream or a TrackUnionStream.
    * SUCCESS
    *    Installation was successful and this listener will start receiving
    *    NotifyRealtimeData on the next AppendToTrack().
    */
   enum class InstallationResult {
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -4,16 +4,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaStreamTrack.h"
 
 #include "DOMMediaStream.h"
 #include "MediaStreamGraph.h"
 #include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
+#include "MediaStreamListener.h"
 
 #ifdef LOG
 #undef LOG
 #endif
 
 static PRLogModuleInfo* gMediaStreamTrackLog;
 #define LOG(type, msg) MOZ_LOG(gMediaStreamTrackLog, type, msg)
 
@@ -156,16 +157,22 @@ MediaStreamTrack::Destroy()
   }
   if (mPrincipalHandleListener) {
     if (GetOwnedStream()) {
       RemoveListener(mPrincipalHandleListener);
     }
     mPrincipalHandleListener->Forget();
     mPrincipalHandleListener = nullptr;
   }
+  for (auto l : mTrackListeners) {
+    RemoveListener(l);
+  }
+  for (auto l : mDirectTrackListeners) {
+    RemoveDirectListener(l);
+  }
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
                                                 DOMEventTargetHelper)
   tmp->Destroy();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningStream)
@@ -382,55 +389,70 @@ MediaStreamTrack::GetInputStream()
   DOMMediaStream* inputDOMStream = GetInputDOMStream();
   MOZ_RELEASE_ASSERT(inputDOMStream->GetInputStream());
   return inputDOMStream->GetInputStream();
 }
 
 ProcessedMediaStream*
 MediaStreamTrack::GetOwnedStream()
 {
+  if (!mOwningStream)
+  {
+    return nullptr;
+  }
+
   return mOwningStream->GetOwnedStream();
 }
 
 void
 MediaStreamTrack::AddListener(MediaStreamTrackListener* aListener)
 {
   LOG(LogLevel::Debug, ("MediaStreamTrack %p adding listener %p",
                         this, aListener));
+  MOZ_ASSERT(GetOwnedStream());
 
   GetOwnedStream()->AddTrackListener(aListener, mTrackID);
+  mTrackListeners.AppendElement(aListener);
 }
 
 void
 MediaStreamTrack::RemoveListener(MediaStreamTrackListener* aListener)
 {
   LOG(LogLevel::Debug, ("MediaStreamTrack %p removing listener %p",
                         this, aListener));
 
-  GetOwnedStream()->RemoveTrackListener(aListener, mTrackID);
+  if (GetOwnedStream()) {
+    GetOwnedStream()->RemoveTrackListener(aListener, mTrackID);
+    mTrackListeners.RemoveElement(aListener);
+  }
 }
 
 void
 MediaStreamTrack::AddDirectListener(DirectMediaStreamTrackListener *aListener)
 {
   LOG(LogLevel::Debug, ("MediaStreamTrack %p (%s) adding direct listener %p to "
                         "stream %p, track %d",
                         this, AsAudioStreamTrack() ? "audio" : "video",
                         aListener, GetOwnedStream(), mTrackID));
+  MOZ_ASSERT(GetOwnedStream());
 
   GetOwnedStream()->AddDirectTrackListener(aListener, mTrackID);
+  mDirectTrackListeners.AppendElement(aListener);
 }
 
 void
 MediaStreamTrack::RemoveDirectListener(DirectMediaStreamTrackListener *aListener)
 {
   LOG(LogLevel::Debug, ("MediaStreamTrack %p removing direct listener %p from stream %p",
                         this, aListener, GetOwnedStream()));
 
-  GetOwnedStream()->RemoveDirectTrackListener(aListener, mTrackID);
+  if (GetOwnedStream()) {
+    GetOwnedStream()->RemoveDirectTrackListener(aListener, mTrackID);
+    mDirectTrackListeners.RemoveElement(aListener);
+  }
 }
 
 already_AddRefed<MediaInputPort>
 MediaStreamTrack::ForwardTrackContentsTo(ProcessedMediaStream* aStream)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(aStream);
   RefPtr<MediaInputPort> port =
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -373,16 +373,18 @@ public:
   already_AddRefed<MediaInputPort> ForwardTrackContentsTo(ProcessedMediaStream* aStream);
 
   /**
    * Returns true if this track is connected to aPort and forwarded to aPort's
    * output stream.
    */
   bool IsForwardedThrough(MediaInputPort* aPort);
 
+  void SetMediaStreamSizeListener(DirectMediaStreamTrackListener* aListener);
+
 protected:
   virtual ~MediaStreamTrack();
 
   void Destroy();
 
   // Returns the original DOMMediaStream's underlying input stream.
   MediaStream* GetInputStream();
 
@@ -411,16 +413,20 @@ protected:
   RefPtr<DOMMediaStream> mOwningStream;
   TrackID mTrackID;
   TrackID mInputTrackID;
   RefPtr<MediaStreamTrackSource> mSource;
   RefPtr<MediaStreamTrack> mOriginalTrack;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIPrincipal> mPendingPrincipal;
   RefPtr<PrincipalHandleListener> mPrincipalHandleListener;
+  // Keep tracking MediaStreamTrackListener and DirectMediaStreamTrackListener,
+  // so we can remove them in |Destory|.
+  nsTArray<RefPtr<MediaStreamTrackListener>> mTrackListeners;
+  nsTArray<RefPtr<DirectMediaStreamTrackListener>> mDirectTrackListeners;
   nsString mID;
   MediaStreamTrackState mReadyState;
   bool mEnabled;
   const bool mRemote;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/MediaTrackList.cpp
+++ b/dom/media/MediaTrackList.cpp
@@ -4,16 +4,17 @@
  * 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/. */
 
 #include "MediaTrack.h"
 #include "MediaTrackList.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/AudioTrack.h"
+#include "mozilla/dom/VideoStreamTrack.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/TrackEvent.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 MediaTrackList::MediaTrackList(nsPIDOMWindowInner* aOwnerWindow,
@@ -101,19 +102,20 @@ MediaTrackList::CreateAudioTrack(const n
                                               aEnabled);
   return track.forget();
 }
 
 already_AddRefed<VideoTrack>
 MediaTrackList::CreateVideoTrack(const nsAString& aId,
                                  const nsAString& aKind,
                                  const nsAString& aLabel,
-                                 const nsAString& aLanguage)
+                                 const nsAString& aLanguage,
+                                 VideoStreamTrack* aVideoTrack)
 {
-  RefPtr<VideoTrack> track = new VideoTrack(aId, aKind, aLabel, aLanguage);
+  RefPtr<VideoTrack> track = new VideoTrack(aId, aKind, aLabel, aLanguage, aVideoTrack);
   return track.forget();
 }
 
 void
 MediaTrackList::EmptyTracks()
 {
   for (uint32_t i = 0; i < mTracks.Length(); ++i) {
     mTracks[i]->SetTrackList(nullptr);
--- a/dom/media/MediaTrackList.h
+++ b/dom/media/MediaTrackList.h
@@ -15,16 +15,17 @@ class DOMMediaStream;
 namespace dom {
 
 class HTMLMediaElement;
 class MediaTrack;
 class AudioTrackList;
 class VideoTrackList;
 class AudioTrack;
 class VideoTrack;
+class VideoStreamTrack;
 
 /**
  * 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.
@@ -53,21 +54,24 @@ public:
 
   static already_AddRefed<AudioTrack>
   CreateAudioTrack(const nsAString& aId,
                    const nsAString& aKind,
                    const nsAString& aLabel,
                    const nsAString& aLanguage,
                    bool aEnabled);
 
+  // For the case of src of HTMLMediaElement is non-MediaStream, leave the
+  // aVideoTrack as default(nullptr).
   static already_AddRefed<VideoTrack>
   CreateVideoTrack(const nsAString& aId,
                    const nsAString& aKind,
                    const nsAString& aLabel,
-                   const nsAString& aLanguage);
+                   const nsAString& aLanguage,
+                   VideoStreamTrack* aVideoTrack = nullptr);
 
   virtual void EmptyTracks();
 
   void CreateAndDispatchChangeEvent();
 
   // WebIDL
   MediaTrack* IndexedGetter(uint32_t aIndex, bool& aFound);
 
--- a/dom/media/VideoTrack.cpp
+++ b/dom/media/VideoTrack.cpp
@@ -1,31 +1,45 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 et tw=78: */
 /* 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/. */
 
 #include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/VideoStreamTrack.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackBinding.h"
 #include "mozilla/dom/VideoTrackList.h"
 
 namespace mozilla {
 namespace dom {
 
 VideoTrack::VideoTrack(const nsAString& aId,
                        const nsAString& aKind,
                        const nsAString& aLabel,
-                       const nsAString& aLanguage)
+                       const nsAString& aLanguage,
+                       VideoStreamTrack* aStreamTarck)
   : MediaTrack(aId, aKind, aLabel, aLanguage)
   , mSelected(false)
+  , mVideoStreamTrack(aStreamTarck)
+{
+}
+
+VideoTrack::~VideoTrack()
 {
 }
 
+NS_IMPL_CYCLE_COLLECTION_INHERITED(VideoTrack, MediaTrack, mVideoStreamTrack)
+
+NS_IMPL_ADDREF_INHERITED(VideoTrack, MediaTrack)
+NS_IMPL_RELEASE_INHERITED(VideoTrack, MediaTrack)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(VideoTrack)
+NS_INTERFACE_MAP_END_INHERITING(MediaTrack)
+
 JSObject*
 VideoTrack::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return VideoTrackBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void VideoTrack::SetSelected(bool aSelected)
 {
--- a/dom/media/VideoTrack.h
+++ b/dom/media/VideoTrack.h
@@ -8,50 +8,63 @@
 #define mozilla_dom_VideoTrack_h
 
 #include "MediaTrack.h"
 
 namespace mozilla {
 namespace dom {
 
 class VideoTrackList;
+class VideoStreamTrack;
 
 class VideoTrack : public MediaTrack
 {
 public:
   VideoTrack(const nsAString& aId,
              const nsAString& aKind,
              const nsAString& aLabel,
-             const nsAString& aLanguage);
+             const nsAString& aLanguage,
+             VideoStreamTrack* aStreamTarck = nullptr);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VideoTrack, MediaTrack)
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   VideoTrack* AsVideoTrack() override
   {
     return this;
   }
 
   // When fetching media resource, if no video track is selected by the media
   // resource, then the first VideoTrack object in the list is set selected as
   // default. If multiple video tracks are selected by its media resource at
   // fetching phase, then the first enabled video track is set selected.
   // aFlags contains FIRE_NO_EVENTS because no events are fired in such cases.
   void SetEnabledInternal(bool aEnabled, int aFlags) override;
 
+  // Get associated video stream track when the video track comes from
+  // MediaStream. This might be nullptr when the src of owning HTMLMediaElement
+  // is not MediaStream.
+  VideoStreamTrack* GetVideoStreamTrack() { return mVideoStreamTrack; }
+
   // WebIDL
   bool Selected() const
   {
     return mSelected;
   }
 
   // Either zero or one video track is selected in a list; If the selected track
   // is in a VideoTrackList, then all the other VideoTrack objects in that list
   // must be unselected.
   void SetSelected(bool aSelected);
 
 private:
+  virtual ~VideoTrack();
+
   bool mSelected;
+  RefPtr<VideoStreamTrack> mVideoStreamTrack;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_VideoTrack_h
--- a/dom/media/VideoTrackList.cpp
+++ b/dom/media/VideoTrackList.cpp
@@ -26,37 +26,37 @@ VideoTrackList::operator[](uint32_t aInd
 void
 VideoTrackList::RemoveTrack(const RefPtr<MediaTrack>& aTrack)
 {
   // we need to find the video track before |MediaTrackList::RemoveTrack|. Or
   // mSelectedIndex will not be valid. The check of mSelectedIndex == -1
   // need to be done after RemoveTrack. Also the call of
   // |MediaTrackList::RemoveTrack| is necessary even when mSelectedIndex = -1.
   bool found;
-  VideoTrack* videoTrack = IndexedGetter(mSelectedIndex, found);
+  VideoTrack* selectedVideoTrack = IndexedGetter(mSelectedIndex, found);
   MediaTrackList::RemoveTrack(aTrack);
   if (mSelectedIndex == -1) {
     // There was no selected track and we don't select another track on removal.
     return;
   }
   MOZ_ASSERT(found, "When mSelectedIndex is set it should point to a track");
-  MOZ_ASSERT(videoTrack, "The mSelectedIndex should be set to video track only");
+  MOZ_ASSERT(selectedVideoTrack, "The mSelectedIndex should be set to video track only");
 
   // Let the caller of RemoveTrack deal with choosing the new selected track if
   // it removes the currently-selected track.
-  if (aTrack == videoTrack) {
+  if (aTrack == selectedVideoTrack) {
     mSelectedIndex = -1;
     return;
   }
 
   // The removed track was not the selected track and there is a
   // currently-selected video track. We need to find the new location of the
   // selected track.
   for (size_t ix = 0; ix < mTracks.Length(); ix++) {
-    if (mTracks[ix] == videoTrack) {
+    if (mTracks[ix] == selectedVideoTrack) {
       mSelectedIndex = ix;
       return;
     }
   }
 }
 
 void
 VideoTrackList::EmptyTracks()