Bug 744896 - Part 2: Enable track interfaces for media elements that are consuming a MediaStream. r=roc,bz
☠☠ backed out by 5b523930b699 ☠ ☠
authorShelly Lin <slin@mozilla.com>
Fri, 23 May 2014 17:34:14 +0800
changeset 192166 d8350c75691040b4d021fce2c835bcfd8a54b0ce
parent 192165 b191be106cae30ec2477531fc53531cb53c35a64
child 192167 7691b13459f4f72dc627af41dc0d83fa189cc7f8
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersroc, bz
bugs744896
milestone33.0a1
Bug 744896 - Part 2: Enable track interfaces for media elements that are consuming a MediaStream. r=roc,bz
content/html/content/public/HTMLMediaElement.h
content/html/content/src/HTMLMediaElement.cpp
content/html/content/src/HTMLVideoElement.cpp
content/media/AudioTrack.cpp
content/media/AudioTrackList.h
content/media/DOMMediaStream.cpp
content/media/DOMMediaStream.h
content/media/MediaTrackList.cpp
content/media/MediaTrackList.h
content/media/VideoTrack.cpp
content/media/VideoTrackList.h
content/media/test/mochitest.ini
content/media/test/test_mediatrack_events_and_consuming_ms.html
dom/webidl/HTMLMediaElement.webidl
--- a/content/html/content/public/HTMLMediaElement.h
+++ b/content/html/content/public/HTMLMediaElement.h
@@ -46,32 +46,35 @@ class ErrorResult;
 class MediaResource;
 class MediaDecoder;
 class VideoFrameContainer;
 namespace dom {
 class MediaKeys;
 class TextTrack;
 class TimeRanges;
 class WakeLock;
+class MediaTrack;
 }
 }
 
 class nsITimer;
 class nsRange;
 class nsIRunnable;
 
 namespace mozilla {
 namespace dom {
 
 // Number of milliseconds between timeupdate events as defined by spec
 #define TIMEUPDATE_MS 250
 
 class MediaError;
 class MediaSource;
 class TextTrackList;
+class AudioTrackList;
+class VideoTrackList;
 
 class HTMLMediaElement : public nsGenericHTMLElement,
                          public nsIObserver,
                          public MediaDecoderOwner,
                          public nsIAudioChannelAgentCallback
 {
 public:
   typedef mozilla::TimeStamp TimeStamp;
@@ -277,16 +280,18 @@ public:
   void NotifyAddedSource();
 
   /**
    * Called when there's been an error fetching the resource. This decides
    * whether it's appropriate to fire an error event.
    */
   void NotifyLoadError();
 
+  void NotifyMediaTrackEnabled(MediaTrack* aTrack);
+
   virtual bool IsNodeOfType(uint32_t aFlags) const MOZ_OVERRIDE;
 
   /**
    * Returns the current load ID. Asynchronous events store the ID that was
    * current when they were enqueued, and if it has changed when they come to
    * fire, they consider themselves cancelled, and don't fire.
    */
   uint32_t GetCurrentLoadID() { return mCurrentLoadID; }
@@ -556,16 +561,20 @@ public:
 
   AudioChannel MozAudioChannelType() const
   {
     return mAudioChannel;
   }
 
   void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
 
+  AudioTrackList* AudioTracks();
+
+  VideoTrackList* VideoTracks();
+
   TextTrackList* TextTracks();
 
   already_AddRefed<TextTrack> AddTextTrack(TextTrackKind aKind,
                                            const nsAString& aLabel,
                                            const nsAString& aLanguage);
 
   void AddTextTrack(TextTrack* aTextTrack) {
     GetOrCreateTextTrackManager()->AddTextTrack(aTextTrack);
@@ -1099,17 +1108,18 @@ protected:
 
   // Playback of the video is paused either due to calling the
   // 'Pause' method, or playback not yet having started.
   WakeLockBoolWrapper mPaused;
 
   enum MutedReasons {
     MUTED_BY_CONTENT               = 0x01,
     MUTED_BY_INVALID_PLAYBACK_RATE = 0x02,
-    MUTED_BY_AUDIO_CHANNEL         = 0x04
+    MUTED_BY_AUDIO_CHANNEL         = 0x04,
+    MUTED_BY_AUDIO_TRACK           = 0x08
   };
 
   uint32_t mMuted;
 
   // True if the media statistics are currently being shown by the builtin
   // video controls
   bool mStatsShowing;
 
@@ -1203,20 +1213,29 @@ protected:
   AudioChannel mAudioChannel;
 
   // The audio channel has been faded.
   bool mAudioChannelFaded;
 
   // Is this media element playing?
   bool mPlayingThroughTheAudioChannel;
 
+  // Disable the video playback by track selection. This flag might not be
+  // enough if we ever expand the ability of supporting multi-tracks video
+  // playback.
+  bool mDisableVideo;
+
   // An agent used to join audio channel service.
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 
   nsRefPtr<TextTrackManager> mTextTrackManager;
 
+  nsRefPtr<AudioTrackList> mAudioTrackList;
+
+  nsRefPtr<VideoTrackList> mVideoTrackList;
+
   MediaWaitingFor mWaitingFor;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLMediaElement_h
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -71,16 +71,20 @@
 #include "MediaMetadataManager.h"
 #include "MediaSourceDecoder.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"
+#include "mozilla/dom/VideoTrack.h"
+#include "mozilla/dom/VideoTrackList.h"
 #include "mozilla/dom/TextTrack.h"
 
 #include "ImageContainer.h"
 #include "nsRange.h"
 #include <algorithm>
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gMediaElementLog;
@@ -420,16 +424,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
   for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mStream);
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
 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();
@@ -441,16 +447,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
   for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams[i].mStream)
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
 
@@ -608,16 +616,23 @@ void HTMLMediaElement::AbortExistingLoad
   mLoadWaitStatus = NOT_WAITING;
 
   // Set a new load ID. This will cause events which were enqueued
   // with a different load ID to silently be cancelled.
   mCurrentLoadID++;
 
   bool fireTimeUpdate = false;
 
+  // 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) {
     fireTimeUpdate = mDecoder->GetCurrentTime() != 0.0;
     ShutdownDecoder();
   }
   if (mSrcStream) {
     EndSrcMediaStreamPlayback();
   }
   if (mMediaSource) {
@@ -853,16 +868,34 @@ void HTMLMediaElement::NotifyLoadError()
   } else if (mSourceLoadCandidate) {
     DispatchAsyncSourceError(mSourceLoadCandidate);
     QueueLoadFromSourceTask();
   } else {
     NS_WARNING("Should know the source we were loading from!");
   }
 }
 
+void HTMLMediaElement::NotifyMediaTrackEnabled(MediaTrack* aTrack)
+{
+  if (!aTrack) {
+    return;
+  }
+
+  // TODO: We are dealing with single audio track and video track for now.
+  if (AudioTrack* track = aTrack->AsAudioTrack()) {
+    if (!track->Enabled()) {
+      SetMutedInternal(mMuted | MUTED_BY_AUDIO_TRACK);
+    } else {
+      SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_TRACK);
+    }
+  } else if (VideoTrack* track = aTrack->AsVideoTrack()) {
+    mDisableVideo = !track->Selected();
+  }
+}
+
 void HTMLMediaElement::LoadFromSourceChildren()
 {
   NS_ASSERTION(mDelayingLoadEvent,
                "Should delay load event (if in document) during load");
   NS_ASSERTION(mIsLoadingFromSourceChildren,
                "Must remember we're loading from source children");
 
   nsIDocument* parentDoc = OwnerDoc()->GetParentDocument();
@@ -2007,16 +2040,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mShuttingDown(false),
     mSuspendedForPreloadNone(false),
     mMediaSecurityVerified(false),
     mCORSMode(CORS_NONE),
     mHasAudio(false),
     mDownloadSuspendedByCache(false),
     mAudioChannelFaded(false),
     mPlayingThroughTheAudioChannel(false),
+    mDisableVideo(false),
     mWaitingFor(MediaWaitingFor::None)
 {
 #ifdef PR_LOGGING
   if (!gMediaElementLog) {
     gMediaElementLog = PR_NewLogModule("nsMediaElement");
   }
   if (!gMediaElementEventsLog) {
     gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
@@ -2782,16 +2816,19 @@ void HTMLMediaElement::SetupSrcMediaStre
   }
   ChangeDelayLoadStatus(false);
   GetSrcMediaStream()->AddAudioOutput(this);
   GetSrcMediaStream()->SetAudioOutputVolume(this, float(mMuted ? 0.0 : mVolume));
   VideoFrameContainer* container = GetVideoFrameContainer();
   if (container) {
     GetSrcMediaStream()->AddVideoOutput(container);
   }
+
+  mSrcStream->ConstructMediaTracks(AudioTracks(), VideoTracks());
+
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
   DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
   DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
   AddRemoveSelfReference();
   // FirstFrameLoaded(false) will be called when the stream has current data,
   // to complete the setup by entering the HAVE_CURRENT_DATA state.
@@ -3969,16 +4006,36 @@ HTMLMediaElement::IsEventAttributeName(n
 }
 
 NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged()
 {
   SetVolumeInternal();
   return NS_OK;
 }
 
+AudioTrackList*
+HTMLMediaElement::AudioTracks()
+{
+  if (!mAudioTrackList) {
+    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(OwnerDoc()->GetParentObject());
+    mAudioTrackList = new AudioTrackList(window, this);
+  }
+  return mAudioTrackList;
+}
+
+VideoTrackList*
+HTMLMediaElement::VideoTracks()
+{
+  if (!mVideoTrackList) {
+    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(OwnerDoc()->GetParentObject());
+    mVideoTrackList = new VideoTrackList(window, this);
+  }
+  return mVideoTrackList;
+}
+
 /* readonly attribute TextTrackList textTracks; */
 TextTrackList*
 HTMLMediaElement::TextTracks()
 {
   return GetOrCreateTextTrackManager()->TextTracks();
 }
 
 already_AddRefed<TextTrack>
--- a/content/html/content/src/HTMLVideoElement.cpp
+++ b/content/html/content/src/HTMLVideoElement.cpp
@@ -74,16 +74,20 @@ HTMLVideoElement::~HTMLVideoElement()
 }
 
 nsresult HTMLVideoElement::GetVideoSize(nsIntSize* size)
 {
   if (mMediaSize.width == -1 && mMediaSize.height == -1) {
     return NS_ERROR_FAILURE;
   }
 
+  if (mDisableVideo) {
+    return NS_ERROR_FAILURE;
+  }
+
   size->height = mMediaSize.height;
   size->width = mMediaSize.width;
   return NS_OK;
 }
 
 bool
 HTMLVideoElement::ParseAttribute(int32_t aNamespaceID,
                                  nsIAtom* aAttribute,
--- a/content/media/AudioTrack.cpp
+++ b/content/media/AudioTrack.cpp
@@ -45,13 +45,18 @@ AudioTrack::SetEnabledInternal(bool aEna
   // If this AudioTrack is no longer in its original AudioTrackList, then
   // whether it is enabled or not has no effect on its original list.
   if (!mList) {
     return;
   }
 
   if (!(aFlags & MediaTrack::FIRE_NO_EVENTS)) {
     mList->CreateAndDispatchChangeEvent();
+
+    HTMLMediaElement* element = mList->GetMediaElement();
+    if (element) {
+      element->NotifyMediaTrackEnabled(this);
+    }
   }
 }
 
 } // namespace dom
 } //namespace mozilla
--- a/content/media/AudioTrackList.h
+++ b/content/media/AudioTrackList.h
@@ -24,14 +24,17 @@ public:
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   AudioTrack* operator[](uint32_t aIndex);
 
   // WebIDL
   AudioTrack* IndexedGetter(uint32_t aIndex, bool& aFound);
 
   AudioTrack* GetTrackById(const nsAString& aId);
+
+protected:
+  virtual AudioTrackList* AsAudioTrackList() MOZ_OVERRIDE { return this; }
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_AudioTrackList_h
--- a/content/media/DOMMediaStream.cpp
+++ b/content/media/DOMMediaStream.cpp
@@ -3,16 +3,20 @@
  * 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 "DOMMediaStream.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/LocalMediaStreamBinding.h"
 #include "mozilla/dom/AudioNode.h"
+#include "mozilla/dom/AudioTrack.h"
+#include "mozilla/dom/AudioTrackList.h"
+#include "mozilla/dom/VideoTrack.h"
+#include "mozilla/dom/VideoTrackList.h"
 #include "MediaStreamGraph.h"
 #include "AudioStreamTrack.h"
 #include "VideoStreamTrack.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream)
@@ -79,21 +83,23 @@ public:
       DOMMediaStream* stream = mListener->GetStream();
       if (!stream) {
         return NS_OK;
       }
 
       nsRefPtr<MediaStreamTrack> track;
       if (mEvents & MediaStreamListener::TRACK_EVENT_CREATED) {
         track = stream->CreateDOMTrack(mID, mType);
+        stream->NotifyMediaStreamTrackCreated(track);
       } else {
         track = stream->GetDOMTrackFor(mID);
       }
       if (mEvents & MediaStreamListener::TRACK_EVENT_ENDED) {
         track->NotifyEnded();
+        stream->NotifyMediaStreamTrackEnded(track);
       }
       return NS_OK;
     }
 
     StreamTime mEndTime;
     nsRefPtr<StreamListener> mListener;
     TrackID mID;
     uint32_t mEvents;
@@ -370,16 +376,101 @@ DOMMediaStream::CheckTracksAvailable()
       // Some expected tracks not available yet. Try this callback again later.
       *mRunOnTracksAvailable.AppendElement() = callbacks[i].forget();
       continue;
     }
     cb->NotifyTracksAvailable(this);
   }
 }
 
+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)
+{
+  if (mHintContents & DOMMediaStream::HINT_CONTENTS_AUDIO) {
+    MediaTrackListListener listener(aAudioTrackList);
+    mMediaTrackListListeners.AppendElement(listener);
+  }
+  if (mHintContents & DOMMediaStream::HINT_CONTENTS_VIDEO) {
+    MediaTrackListListener listener(aVideoTrackList);
+    mMediaTrackListListeners.AppendElement(listener);
+  }
+
+  int firstEnabledVideo = -1;
+  for (uint32_t i = 0; i < mTracks.Length(); ++i) {
+    if (AudioStreamTrack* t = mTracks[i]->AsAudioStreamTrack()) {
+      nsRefPtr<AudioTrack> track = CreateAudioTrack(t);
+      aAudioTrackList->AddTrack(track);
+    } else if (VideoStreamTrack* t = mTracks[i]->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::NotifyMediaStreamTrackCreated(MediaStreamTrack* 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)
+{
+  nsAutoString id;
+  aTrack->GetId(id);
+  for (uint32_t i = 0; i < mMediaTrackListListeners.Length(); ++i) {
+    mMediaTrackListListeners[i].NotifyMediaTrackEnded(id);
+  }
+}
+
 DOMLocalMediaStream::~DOMLocalMediaStream()
 {
   if (mStream) {
     // Make sure Listeners of this stream know it's going away
     Stop();
   }
 }
 
--- a/content/media/DOMMediaStream.h
+++ b/content/media/DOMMediaStream.h
@@ -32,30 +32,40 @@ namespace mozilla {
 
 class MediaStream;
 
 namespace dom {
 class AudioNode;
 class MediaStreamTrack;
 class AudioStreamTrack;
 class VideoStreamTrack;
+class AudioTrack;
+class VideoTrack;
+class AudioTrackList;
+class VideoTrackList;
+class MediaTrackListListener;
 }
 
 class MediaStreamDirectListener;
 
 /**
  * DOM wrapper for MediaStreams.
  */
 class DOMMediaStream : public nsIDOMMediaStream,
                        public nsWrapperCache
 {
   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 uint8_t TrackTypeHints;
 
   DOMMediaStream();
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMMediaStream)
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -202,23 +212,37 @@ 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);
+
+  void NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack);
+
+  void NotifyMediaStreamTrackEnded(MediaStreamTrack* aTrack);
+
 protected:
   virtual ~DOMMediaStream();
 
   void Destroy();
   void InitSourceStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents);
   void InitTrackUnionStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents);
   void InitStreamCommon(MediaStream* aStream);
+  already_AddRefed<AudioTrack> CreateAudioTrack(AudioStreamTrack* aStreamTrack);
+  already_AddRefed<VideoTrack> CreateVideoTrack(VideoStreamTrack* aStreamTrack);
 
   void CheckTracksAvailable();
 
   class StreamListener;
   friend class StreamListener;
 
   // StreamTime at which the currentTime attribute would return 0.
   StreamTime mLogicalStreamStartTime;
@@ -239,16 +263,20 @@ protected:
   nsTArray<nsCOMPtr<nsISupports> > mConsumersToKeepAlive;
 
   // Indicate what track types we eventually expect to add to this stream
   uint8_t mHintContents;
   // Indicate what track types have been added to this stream
   uint8_t mTrackTypesAvailable;
   bool mNotifiedOfMediaStreamGraphShutdown;
 
+  // Send notifications to AudioTrackList or VideoTrackList, if this MediaStream
+  // is consumed by a HTMLMediaElement.
+  nsTArray<MediaTrackListListener> mMediaTrackListListeners;
+
 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;
   // this is used in gUM and WebRTC to identify peers that this stream
--- a/content/media/MediaTrackList.cpp
+++ b/content/media/MediaTrackList.cpp
@@ -2,24 +2,52 @@
 /* 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 "MediaTrack.h"
 #include "MediaTrackList.h"
 #include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/HTMLMediaElement.h"
 #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()
@@ -72,16 +100,38 @@ MediaTrackList::AddTrack(MediaTrack* aTr
 void
 MediaTrackList::RemoveTrack(const nsRefPtr<MediaTrack>& aTrack)
 {
   mTracks.RemoveElement(aTrack);
   aTrack->SetTrackList(nullptr);
   CreateAndDispatchTrackEventRunner(aTrack, NS_LITERAL_STRING("removetrack"));
 }
 
+already_AddRefed<AudioTrack>
+MediaTrackList::CreateAudioTrack(const nsAString& aId,
+                                 const nsAString& aKind,
+                                 const nsAString& aLabel,
+                                 const nsAString& aLanguage,
+                                 bool aEnabled)
+{
+  nsRefPtr<AudioTrack> track = new AudioTrack(aId, aKind, aLabel, aLanguage,
+                                              aEnabled);
+  return track.forget();
+}
+
+already_AddRefed<VideoTrack>
+MediaTrackList::CreateVideoTrack(const nsAString& aId,
+                                 const nsAString& aKind,
+                                 const nsAString& aLabel,
+                                 const nsAString& aLanguage)
+{
+  nsRefPtr<VideoTrack> track = new VideoTrack(aId, aKind, aLabel, aLanguage);
+  return track.forget();
+}
+
 void
 MediaTrackList::EmptyTracks()
 {
   for (uint32_t i = 0; i < mTracks.Length(); ++i) {
     mTracks[i]->SetTrackList(nullptr);
   }
   mTracks.Clear();
 }
--- a/content/media/MediaTrackList.h
+++ b/content/media/MediaTrackList.h
@@ -11,16 +11,51 @@
 
 namespace mozilla {
 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:
+  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.
@@ -39,16 +74,29 @@ public:
   // The return value is non-null, assert an error if aIndex is out of bound
   // for array mTracks.
   MediaTrack* operator[](uint32_t aIndex);
 
   void AddTrack(MediaTrack* aTrack);
 
   void RemoveTrack(const nsRefPtr<MediaTrack>& aTrack);
 
+  static already_AddRefed<AudioTrack>
+  CreateAudioTrack(const nsAString& aId,
+                   const nsAString& aKind,
+                   const nsAString& aLabel,
+                   const nsAString& aLanguage,
+                   bool aEnabled);
+
+  static already_AddRefed<VideoTrack>
+  CreateVideoTrack(const nsAString& aId,
+                   const nsAString& aKind,
+                   const nsAString& aLabel,
+                   const nsAString& aLanguage);
+
   virtual void EmptyTracks();
 
   void CreateAndDispatchChangeEvent();
 
   // WebIDL
   MediaTrack* IndexedGetter(uint32_t aIndex, bool& aFound);
 
   MediaTrack* GetTrackById(const nsAString& aId);
@@ -57,20 +105,30 @@ 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:
   void CreateAndDispatchTrackEventRunner(MediaTrack* aTrack,
                                          const nsAString& aEventName);
 
+  virtual AudioTrackList* AsAudioTrackList() { return nullptr; }
+
+  virtual VideoTrackList* AsVideoTrackList() { return nullptr; }
+
+  HTMLMediaElement* GetMediaElement() { return mMediaElement; }
+
   nsTArray<nsRefPtr<MediaTrack>> mTracks;
   nsRefPtr<HTMLMediaElement> mMediaElement;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_MediaTrackList_h
--- a/content/media/VideoTrack.cpp
+++ b/content/media/VideoTrack.cpp
@@ -67,13 +67,18 @@ VideoTrack::SetEnabledInternal(bool aEna
   } else {
     list.mSelectedIndex = -1;
   }
 
   // Fire the change event at selection changes on this video track, shall
   // propose a spec change later.
   if (!(aFlags & MediaTrack::FIRE_NO_EVENTS)) {
     list.CreateAndDispatchChangeEvent();
+
+    HTMLMediaElement* element = mList->GetMediaElement();
+    if (element) {
+      element->NotifyMediaTrackEnabled(this);
+    }
   }
 }
 
 } // namespace dom
 } //namespace mozilla
--- a/content/media/VideoTrackList.h
+++ b/content/media/VideoTrackList.h
@@ -36,16 +36,19 @@ public:
   }
 
   VideoTrack* IndexedGetter(uint32_t aIndex, bool& aFound);
 
   VideoTrack* GetTrackById(const nsAString& aId);
 
   friend class VideoTrack;
 
+protected:
+  virtual VideoTrackList* AsVideoTrackList() MOZ_OVERRIDE { return this; }
+
 private:
   int32_t mSelectedIndex;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_VideoTrackList_h
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -374,16 +374,17 @@ skip-if = buildapp == 'b2g' || toolkit =
 [test_mediarecorder_record_nosrc.html]
 [test_mediarecorder_record_session.html]
 [test_mediarecorder_record_startstopstart.html]
 [test_mediarecorder_record_stopms.html]
 [test_mediarecorder_record_timeslice.html]
 [test_mediarecorder_reload_crash.html]
 [test_mediarecorder_unsupported_src.html]
 [test_mediarecorder_record_getdata_afterstart.html]
+[test_mediatrack_events_and_consuming_ms.html]
 [test_metadata.html]
 [test_mixed_principals.html]
 skip-if = true # bug 567954 and intermittent leaks
 [test_mozHasAudio.html]
 [test_networkState.html]
 [test_new_audio.html]
 [test_no_load_event.html]
 [test_paused.html]
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_mediatrack_events_and_consuming_ms.html
@@ -0,0 +1,189 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test track interfaces when consuming a MediaStream from gUM</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function startTest() {
+  navigator.mozGetUserMedia({audio:true, video:true, fake:true},
+    function(stream) {
+      var audioStreamTracks = stream.getAudioTracks();
+      var videoStreamTracks = stream.getVideoTracks();
+
+      var audioOnchange = 0;
+      var audioOnaddtrack = 0;
+      var audioOnremovetrack = 0;
+      var videoOnchange = 0;
+      var videoOnaddtrack = 0;
+      var videoOnremovetrack = 0;
+
+      var element = document.createElement("video");
+      isnot(element.audioTracks, undefined, "HTMLMediaElement::AudioTracks() property should be available.");
+      isnot(element.videoTracks, undefined, "HTMLMediaElement::VideoTracks() property should be available.");
+
+      function verify_event(e, type) {
+        is(e.type, type, "Event type should be " + type);
+        ok(e.isTrusted, "Event should be trusted.");
+        ok(!e.bubbles, "Event shouldn't bubble.");
+        ok(!e.cancelable, "Event shouldn't be cancelable.");
+      }
+
+      function setAudioEnabled(enabled, index) {
+        return new Promise(function(resolve, reject) {
+          element.audioTracks[index].enabled = enabled;
+          element.audioTracks.onchange = function(e) {
+            ok(e instanceof window.Event, "Event fired from onchange should be a simple event.");
+            ok(true, 'onchange is expected to be called from audioTracks.');
+            verify_event(e, "change");
+            audioOnchange++;
+            resolve();
+          }
+        });
+      }
+
+      function setVideoSelected(selected, index) {
+        return new Promise(function(resolve, reject) {
+          element.videoTracks[index].selected = selected;
+          element.videoTracks.onchange = function(e) {
+            ok(e instanceof window.Event, "Event fired from onchange should be a simple event.");
+            ok(true, 'onchange is expected to be called from videoTracks.');
+            verify_event(e, "change");
+
+            var noVideoSelected = true;
+            for (var i=0; i < element.videoTracks.length; ++i) {
+              var track = element.videoTracks[i];
+              if (track.selected == true) {
+                noVideoSelected = false;
+                break;
+              }
+            }
+
+            if (selected) {
+              is(element.videoTracks.selectedIndex, index,
+                 'SelectedIndex shuld be '+index+' if video track is set selected.');
+            } else {
+              if (noVideoSelected) {
+                is(element.videoTracks.selectedIndex, -1,
+                   'SelectedIndex shuld be -1 if no video track is set selected.');
+              } else {
+                reject();
+              }
+            }
+            videoOnchange++;
+            resolve();
+          }
+        });
+      }
+
+      element.audioTracks.onaddtrack = function(e) {
+        ok(e instanceof TrackEvent, "Event fired from onaddtrack should be a TrackEvent");
+        ok(true, 'onaddtrack is expected to be called from audioTracks.');
+        verify_event(e, "addtrack");
+        audioOnaddtrack++;
+      }
+
+      element.audioTracks.onremovetrack = function(e) {
+        ok(e instanceof TrackEvent, "Event fired from onremovetrack should be a TrackEvent");
+        ok(true, 'onremovetrack is expected to be called from audioTracks.');
+        verify_event(e, "removetrack");
+        audioOnremovetrack++;
+      }
+
+      element.videoTracks.onaddtrack = function(e) {
+        ok(e instanceof TrackEvent, "Event fired from onaddtrack should be a TrackEvent");
+        ok(true, 'onaddtrack is expected to be called from videoTracks.');
+        verify_event(e, "addtrack");
+        videoOnaddtrack++;
+      }
+
+      element.videoTracks.onremovetrack = function(e) {
+        ok(e instanceof TrackEvent, "Event fired from onremovetrack should be a TrackEvent");
+        ok(true, 'onremovetrack is expected to be called from videoTracks.');
+        verify_event(e, "removetrack");
+        videoOnremovetrack++;
+      }
+
+
+      element.onended = function() {
+        is(audioOnchange, 2, 'change event on audioTracks should fired twice.');
+        is(videoOnchange, 2, 'change event on videoTracks should fired twice.');
+
+        is(audioOnremovetrack, audioStreamTracks.length,
+           'Calls of onremovetrack from audioTracks should match the numbers of AudioStreamTrack.');
+        is(videoOnremovetrack, videoStreamTracks.length,
+           'Calls of onremovetrack from videoTracks should match the numbers of VideoStreamTrack.');
+
+        SimpleTest.finish();
+      }
+
+      var promise = new Promise(function(resolve, reject) {
+        element.mozSrcObject = stream;
+        element.play();
+
+        element.onloadedmetadata = function() {
+          is(audioOnaddtrack, audioStreamTracks.length,
+             'Calls of onaddtrack from audioTracks should match the numbers of AudioStreamTrack.');
+          is(videoOnaddtrack, videoStreamTracks.length,
+             'Calls of onaddtrack from videoTracks should match the numbers of VideoStreamTrack.');
+
+          is(element.audioTracks.length, audioStreamTracks.length,
+            'Length of audioTracks should be the same as the length of AudioStreamTrack.');
+          is(element.videoTracks.length, videoStreamTracks.length,
+            'Length of videoTracks should be the same as the length of VideoStreamTrack.');
+
+          for (var i=0; i < audioStreamTracks.length; ++i) {
+            var track = element.audioTracks.getTrackById(audioStreamTracks[i].id);
+            isnot(track, null, 'Successfully get '+ track.id + ' from audioTracks.');
+          }
+          for (var i=0; i < videoStreamTracks.length; ++i) {
+            var track = element.videoTracks.getTrackById(videoStreamTracks[i].id);
+            isnot(track, null, 'Successfully get '+ track.id + ' from videoTracks.');
+          }
+
+          is(element.videoTracks.selectedIndex, 0,
+             'The first video track is set selected as default.');
+
+          resolve();
+        }
+      });
+
+      promise.then(function() {
+        var p1 = setAudioEnabled(false, 0);
+        var p2 = setVideoSelected(false, 0);
+        return Promise.all([p1, p2]);
+      }).catch(function(err) {
+        ok(false, 'Something went wrong in onchange callback.');
+      }).then(function() {
+        var p3 = setAudioEnabled(true, 0);
+        var p4 = setVideoSelected(true, 0);
+        return Promise.all([p3, p4]);
+      }).catch(function(err) {
+        ok(false, 'Something went wrong in onchange callback.');
+      }).then(function() {
+        stream.stop();
+      });
+    },
+    function(err) {
+      ok(false, 'Unexpected error fired with: ' + err);
+      SimpleTest.finish();
+    }
+  );
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+  {
+    "set": [
+      ["media.track.enabled", true]
+    ]
+  }, startTest);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -81,18 +81,20 @@ interface HTMLMediaElement : HTMLElement
   [SetterThrows]
            attribute double volume;
            attribute boolean muted;
   [SetterThrows]
            attribute boolean defaultMuted;
 
   // TODO: Bug 847379
   // tracks
-  //readonly attribute AudioTrackList audioTracks;
-  //readonly attribute VideoTrackList videoTracks;
+  [Pref="media.track.enabled"]
+  readonly attribute AudioTrackList audioTracks;
+  [Pref="media.track.enabled"]
+  readonly attribute VideoTrackList videoTracks;
   [Pref="media.webvtt.enabled"]
   readonly attribute TextTrackList textTracks;
   [Pref="media.webvtt.enabled"]
   TextTrack addTextTrack(TextTrackKind kind,
                          optional DOMString label = "",
                          optional DOMString language = "");
 };