Bug 1208378 - Implement MediaStreamTrack's muted state and events. r?jib, r?smaug draft
authorAndreas Pehrson <pehrsons@mozilla.com>
Mon, 06 Nov 2017 19:03:52 +0100
changeset 695693 a947e44c14698195189fdd5e4f9e044713526595
parent 695692 002346b4c0df07e51c0afa5e7bb37a261eaca018
child 695694 84ba04371f847db9695f8f2727aafd008794f6a9
push id88502
push userbmo:apehrson@mozilla.com
push dateThu, 09 Nov 2017 17:30:09 +0000
reviewersjib, smaug
bugs1208378
milestone58.0a1
Bug 1208378 - Implement MediaStreamTrack's muted state and events. r?jib, r?smaug MozReview-Commit-ID: 1Sp9utMnWXI
dom/base/nsGkAtomList.h
dom/html/HTMLMediaElement.cpp
dom/media/DOMMediaStream.cpp
dom/media/MediaStreamTrack.cpp
dom/media/MediaStreamTrack.h
dom/webidl/MediaStreamTrack.webidl
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -721,16 +721,17 @@ GK_ATOM(mozfullscreenerror, "mozfullscre
 GK_ATOM(mozpointerlockchange, "mozpointerlockchange")
 GK_ATOM(mozpointerlockerror, "mozpointerlockerror")
 GK_ATOM(mozprivatebrowsing, "mozprivatebrowsing")
 GK_ATOM(moz_opaque, "moz-opaque")
 GK_ATOM(moz_action_hint, "mozactionhint")
 GK_ATOM(x_moz_errormessage, "x-moz-errormessage")
 GK_ATOM(multicol, "multicol")
 GK_ATOM(multiple, "multiple")
+GK_ATOM(mute, "mute")
 GK_ATOM(muted, "muted")
 GK_ATOM(name, "name")
 GK_ATOM(_namespace, "namespace")
 GK_ATOM(namespaceAlias, "namespace-alias")
 GK_ATOM(namespaceUri, "namespace-uri")
 GK_ATOM(NaN, "NaN")
 GK_ATOM(nativeAnonymousChildList, "nativeAnonymousChildList")
 GK_ATOM(nav, "nav")
@@ -941,16 +942,17 @@ GK_ATOM(onmozpointerlockerror, "onmozpoi
 GK_ATOM(onMozMousePixelScroll, "onMozMousePixelScroll")
 GK_ATOM(onMozScrolledAreaChanged, "onMozScrolledAreaChanged")
 GK_ATOM(onmapfolderlistingreq, "onmapfolderlistingreq")
 GK_ATOM(onmapmessageslistingreq, "onmapmessageslistingreq")
 GK_ATOM(onmapgetmessagereq, "onmapgetmessagereq")
 GK_ATOM(onmapsetmessagestatusreq, "onmapsetmessagestatusreq")
 GK_ATOM(onmapsendmessagereq, "onmapsendmessagereq")
 GK_ATOM(onmapmessageupdatereq, "onmapmessageupdatereq")
+GK_ATOM(onmute, "onmute")
 GK_ATOM(onnewrdsgroup, "onnewrdsgroup")
 GK_ATOM(onnotificationclick, "onnotificationclick")
 GK_ATOM(onnotificationclose, "onnotificationclose")
 GK_ATOM(onnoupdate, "onnoupdate")
 GK_ATOM(onobexpasswordreq, "onobexpasswordreq")
 GK_ATOM(onobsolete, "onobsolete")
 GK_ATOM(ononline, "ononline")
 GK_ATOM(onoffline, "onoffline")
@@ -1032,16 +1034,17 @@ GK_ATOM(ontouchend, "ontouchend")
 GK_ATOM(ontouchmove, "ontouchmove")
 GK_ATOM(ontouchcancel, "ontouchcancel")
 GK_ATOM(ontransitioncancel, "ontransitioncancel")
 GK_ATOM(ontransitionend, "ontransitionend")
 GK_ATOM(ontransitionrun, "ontransitionrun")
 GK_ATOM(ontransitionstart, "ontransitionstart")
 GK_ATOM(onunderflow, "onunderflow")
 GK_ATOM(onunload, "onunload")
+GK_ATOM(onunmute, "onunmute")
 GK_ATOM(onupdatefound, "onupdatefound")
 GK_ATOM(onupdateready, "onupdateready")
 GK_ATOM(onupgradeneeded, "onupgradeneeded")
 GK_ATOM(onussdreceived, "onussdreceived")
 GK_ATOM(onversionchange, "onversionchange")
 GK_ATOM(onvisibilitychange, "onvisibilitychange")
 GK_ATOM(onvoicechange, "onvoicechange")
 GK_ATOM(onvoiceschanged, "onvoiceschanged")
@@ -1383,16 +1386,17 @@ GK_ATOM(tty, "tty")
 GK_ATOM(tv, "tv")
 GK_ATOM(type, "type")
 GK_ATOM(typemustmatch, "typemustmatch")
 GK_ATOM(u, "u")
 GK_ATOM(ul, "ul")
 GK_ATOM(underflow, "underflow")
 GK_ATOM(undetermined, "undetermined")
 GK_ATOM(unload, "unload")
+GK_ATOM(unmute, "unmute")
 GK_ATOM(unparsedEntityUri, "unparsed-entity-uri")
 GK_ATOM(upperAlpha, "upper-alpha")
 GK_ATOM(upperFirst, "upper-first")
 GK_ATOM(upperRoman, "upper-roman")
 GK_ATOM(uri, "uri")
 GK_ATOM(use, "use")
 GK_ATOM(useAttributeSets, "use-attribute-sets")
 GK_ATOM(usemap, "usemap")
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -3189,16 +3189,26 @@ public:
       // This could happen during shutdown.
       return;
     }
 
     mPrincipal = mCapturedTrackSource->GetPrincipal();
     MediaStreamTrackSource::PrincipalChanged();
   }
 
+  void MutedChanged(bool aNewState) override
+  {
+    if (!mCapturedTrackSource) {
+      // This could happen during shutdown.
+      return;
+    }
+
+    MediaStreamTrackSource::MutedChanged(aNewState);
+  }
+
 private:
   virtual ~StreamCaptureTrackSource() {}
 
   RefPtr<HTMLMediaElement> mElement;
   RefPtr<MediaStreamTrackSource> mCapturedTrackSource;
   RefPtr<DOMMediaStream> mOwningStream;
   TrackID mDestinationTrackID;
 };
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -1199,16 +1199,17 @@ DOMMediaStream::CloneDOMTrack(MediaStrea
     new TrackPort(inputPort, newTrack, TrackPort::InputPortOwnership::OWNED));
 
   mTracks.AppendElement(
     new TrackPort(mPlaybackPort, newTrack, TrackPort::InputPortOwnership::EXTERNAL));
 
   NotifyTrackAdded(newTrack);
 
   newTrack->SetEnabled(aTrack.Enabled());
+  newTrack->SetMuted(aTrack.Muted());
   newTrack->SetReadyState(aTrack.ReadyState());
 
   if (aTrack.Ended()) {
     // For extra suspenders, make sure that we don't forward data by mistake
     // to the clone when the original has already ended.
     // We only block END_EXISTING to allow any pending clones to end.
     RefPtr<Pledge<bool, nsresult>> blockingPledge =
       inputPort->BlockSourceTrackId(inputTrackID,
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -119,17 +119,17 @@ protected:
 MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
                                    TrackID aInputTrackID,
                                    MediaStreamTrackSource* aSource,
                                    const MediaTrackConstraints& aConstraints)
   : mOwningStream(aStream), mTrackID(aTrackID),
     mInputTrackID(aInputTrackID), mSource(aSource),
     mPrincipal(aSource->GetPrincipal()),
     mReadyState(MediaStreamTrackState::Live),
-    mEnabled(true), mConstraints(aConstraints)
+    mEnabled(true), mMuted(false), mConstraints(aConstraints)
 {
   GetSource().RegisterSink(this);
 
   if (GetOwnedStream()) {
     mPrincipalHandleListener = new PrincipalHandleListener(this);
     AddListener(mPrincipalHandleListener);
   }
 
@@ -360,16 +360,38 @@ MediaStreamTrack::NotifyPrincipalHandleC
                        mPrincipal.get(), mPendingPrincipal.get()));
   if (PrincipalHandleMatches(handle, mPendingPrincipal)) {
     SetPrincipal(mPendingPrincipal);
     mPendingPrincipal = nullptr;
   }
 }
 
 void
+MediaStreamTrack::MutedChanged(bool aNewState)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  RefPtr<MediaStreamTrack> track = this;
+  NS_DispatchToMainThread(NewRunnableFrom([track, aNewState]() mutable {
+    if (track->mMuted == aNewState) {
+      MOZ_ASSERT_UNREACHABLE("Muted state didn't actually change");
+      return NS_OK;
+    }
+    track->mMuted = aNewState;
+    nsString eventName;
+    if (aNewState) {
+      eventName = NS_LITERAL_STRING("mute");
+    } else {
+      eventName = NS_LITERAL_STRING("unmute");
+    }
+    track->DispatchTrustedEvent(eventName);
+    return NS_OK;
+  }));
+}
+
+void
 MediaStreamTrack::NotifyEnded()
 {
   MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);
 
   auto consumers(mConsumers);
   for (const auto& consumer : consumers) {
     if (consumer) {
       consumer->NotifyEnded(this);
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -56,16 +56,17 @@ class MediaStreamTrackSource : public ns
   NS_DECL_CYCLE_COLLECTION_CLASS(MediaStreamTrackSource)
 
 public:
   class Sink : public SupportsWeakPtr<Sink>
   {
   public:
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(MediaStreamTrackSource::Sink)
     virtual void PrincipalChanged() = 0;
+    virtual void MutedChanged(bool aNewState) = 0;
   };
 
   MediaStreamTrackSource(nsIPrincipal* aPrincipal,
                          const nsString& aLabel)
     : mPrincipal(aPrincipal),
       mLabel(aLabel),
       mStopped(false)
   {
@@ -190,16 +191,35 @@ protected:
         MOZ_ASSERT_UNREACHABLE("Sink was not explicitly removed");
         mSinks.RemoveElement(sink);
         continue;
       }
       sink->PrincipalChanged();
     }
   }
 
+  /**
+   * Called by a sub class when the source's muted state has changed. Note that
+   * the source is responsible for making the content black/silent during mute.
+   * Notifies all sinks.
+   */
+  void MutedChanged(bool aNewState)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    nsTArray<WeakPtr<Sink>> sinks(mSinks);
+    for (auto& sink : sinks) {
+      if (!sink) {
+        MOZ_ASSERT_UNREACHABLE("Sink was not explicitly removed");
+        mSinks.RemoveElement(sink);
+        continue;
+      }
+      sink->MutedChanged(aNewState);
+    }
+  }
+
   // Principal identifying who may access the contents of this source.
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   // Currently registered sinks.
   nsTArray<WeakPtr<Sink>> mSinks;
 
   // The label of the track we are the source of per the MediaStreamTrack spec.
   const nsString mLabel;
@@ -292,26 +312,29 @@ public:
   virtual const VideoStreamTrack* AsVideoStreamTrack() const { return nullptr; }
 
   // WebIDL
   virtual void GetKind(nsAString& aKind) = 0;
   void GetId(nsAString& aID) const;
   void GetLabel(nsAString& aLabel) { GetSource().GetLabel(aLabel); }
   bool Enabled() { return mEnabled; }
   void SetEnabled(bool aEnabled);
+  bool Muted() { return mMuted; }
   void Stop();
   void GetConstraints(dom::MediaTrackConstraints& aResult);
   void GetSettings(dom::MediaTrackSettings& aResult);
 
   already_AddRefed<Promise>
   ApplyConstraints(const dom::MediaTrackConstraints& aConstraints,
                    CallerType aCallerType, ErrorResult &aRv);
   already_AddRefed<MediaStreamTrack> Clone();
   MediaStreamTrackState ReadyState() { return mReadyState; }
 
+  IMPL_EVENT_HANDLER(mute)
+  IMPL_EVENT_HANDLER(unmute)
   IMPL_EVENT_HANDLER(ended)
 
   /**
    * Convenience (and legacy) method for when ready state is "ended".
    */
   bool Ended() const { return mReadyState == MediaStreamTrackState::Ended; }
 
   /**
@@ -367,16 +390,26 @@ public:
   }
 
   // Webrtc allows the remote side to name tracks whatever it wants, and we
   // need to surface this to content.
   void AssignId(const nsAString& aID) { mID = aID; }
 
   // Implementation of MediaStreamTrackSource::Sink
   void PrincipalChanged() override;
+  /**
+   * 4.3.1 Life-cycle and Media flow - Media flow
+   * To update a track's muted state to `newState`, the User Agent MUST queue
+   * a task to run the following steps:
+   *  1. Let track be the MediaStreamTrack in question.
+   *  2. Set track's muted attribute to newState.
+   *  3. If newState is true let eventName be mute, otherwise unmute.
+   *  4. Fire a simple event named eventName on track.
+   */
+  void MutedChanged(bool aNewState) override;
 
   /**
    * Add a PrincipalChangeObserver to this track.
    *
    * Returns true if it was successfully added.
    *
    * Ownership of the PrincipalChangeObserver remains with the caller, and it's
    * the caller's responsibility to remove the observer before it dies.
@@ -436,16 +469,21 @@ public:
    */
   bool IsForwardedThrough(MediaInputPort* aPort);
 
   void SetMediaStreamSizeListener(DirectMediaStreamTrackListener* aListener);
 
 protected:
   virtual ~MediaStreamTrack();
 
+  /**
+   * Sets this track's muted state without raising any events.
+   */
+  void SetMuted(bool aMuted) { mMuted = aMuted; }
+
   void Destroy();
 
   // Returns the original DOMMediaStream's underlying input stream.
   MediaStream* GetInputStream();
 
   // Returns the owning DOMMediaStream's underlying owned stream.
   ProcessedMediaStream* GetOwnedStream();
 
@@ -480,15 +518,16 @@ protected:
   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;
+  bool mMuted;
   dom::MediaTrackConstraints mConstraints;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* MEDIASTREAMTRACK_H_ */
--- a/dom/webidl/MediaStreamTrack.webidl
+++ b/dom/webidl/MediaStreamTrack.webidl
@@ -74,21 +74,19 @@ enum MediaStreamTrackState {
 };
 
 [Exposed=Window]
 interface MediaStreamTrack : EventTarget {
     readonly    attribute DOMString             kind;
     readonly    attribute DOMString             id;
     readonly    attribute DOMString             label;
                 attribute boolean               enabled;
-//  readonly    attribute boolean               muted;
-//              attribute EventHandler          onmute;
-//              attribute EventHandler          onunmute;
-//  readonly    attribute boolean               _readonly;
-//  readonly    attribute boolean               remote;
+    readonly    attribute boolean               muted;
+                attribute EventHandler          onmute;
+                attribute EventHandler          onunmute;
     readonly    attribute MediaStreamTrackState readyState;
                 attribute EventHandler          onended;
     MediaStreamTrack       clone ();
     void                   stop ();
 //  MediaTrackCapabilities getCapabilities ();
     MediaTrackConstraints  getConstraints ();
     MediaTrackSettings     getSettings ();