Bug 1208316 - Route notifications of ending tracks through MediaStreamTrack instead of DOMMediaStream. r?jib draft
authorAndreas Pehrson <pehrsons@gmail.com>
Fri, 23 Sep 2016 17:03:13 +0200
changeset 432201 e1adfff81a6ffcd419c03054d2597df1a3b0792d
parent 432200 73e3e8e8b0ae467e5740c2a65a672bc7b1dc098b
child 432202 d211e77ab48ea2f83614901ae464457aa9242f31
push id34233
push userbmo:pehrson@telenordigital.com
push dateTue, 01 Nov 2016 13:21:40 +0000
reviewersjib
bugs1208316
milestone52.0a1
Bug 1208316 - Route notifications of ending tracks through MediaStreamTrack instead of DOMMediaStream. r?jib MozReview-Commit-ID: KMFE5HXEOtJ
dom/media/DOMMediaStream.cpp
dom/media/DOMMediaStream.h
dom/media/MediaStreamTrack.cpp
dom/media/MediaStreamTrack.h
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -231,114 +231,121 @@ public:
   {}
 
   void Forget()
   {
     MOZ_ASSERT(NS_IsMainThread());
     mStream = nullptr;
   }
 
-  void DoNotifyTrackEnded(MediaStream* aInputStream,
-                          TrackID aInputTrackID)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!mStream) {
-      return;
-    }
-
-    LOG(LogLevel::Debug, ("DOMMediaStream %p Track %u of stream %p ended",
-                          mStream, aInputTrackID, aInputStream));
-
-    RefPtr<MediaStreamTrack> track =
-      mStream->FindPlaybackDOMTrack(aInputStream, aInputTrackID);
-    if (!track) {
-      LOG(LogLevel::Debug, ("DOMMediaStream %p Not a playback track.", mStream));
-      return;
-    }
-
-    LOG(LogLevel::Debug, ("DOMMediaStream %p Playback track; notifying stream listeners.",
-                           mStream));
-    mStream->NotifyTrackRemoved(track);
-
-    RefPtr<TrackPort> endedPort = mStream->FindPlaybackTrackPort(*track);
-    NS_ASSERTION(endedPort, "Playback track should have a TrackPort");
-    if (endedPort && IsTrackIDExplicit(endedPort->GetSourceTrackId())) {
-      // If a track connected to a locked-track input port ends, we destroy the
-      // port to allow our playback stream to finish.
-      // XXX (bug 1208316) This should not be necessary when MediaStreams don't
-      // finish but instead become inactive.
-      endedPort->DestroyInputPort();
-    }
-  }
-
   void DoNotifyFinishedTrackCreation()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mStream) {
       return;
     }
 
     // The owned stream listener adds its tracks after another main thread
     // dispatch. We have to do the same to notify of created tracks to stay
     // in sync. (Or NotifyTracksCreated is called before tracks are added).
     NS_DispatchToMainThread(
         NewRunnableMethod(mStream, &DOMMediaStream::NotifyTracksCreated));
   }
 
-  // The methods below are called on the MediaStreamGraph thread.
-
-  void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
-                                StreamTime aTrackOffset, TrackEventCommand aTrackEvents,
-                                const MediaSegment& aQueuedMedia,
-                                MediaStream* aInputStream,
-                                TrackID aInputTrackID) override
+  void DoNotifyFinished()
   {
-    if (aTrackEvents & TrackEventCommand::TRACK_EVENT_ENDED) {
-      nsCOMPtr<nsIRunnable> runnable =
-        NewRunnableMethod<RefPtr<MediaStream>, TrackID>(
-          this, &PlaybackStreamListener::DoNotifyTrackEnded, aInputStream, aInputTrackID);
-      aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (!mStream) {
+      return;
     }
+
+    mStream->NotifyFinished();
   }
 
+  // The methods below are called on the MediaStreamGraph thread.
+
   void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override
   {
     nsCOMPtr<nsIRunnable> runnable =
       NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinishedTrackCreation);
     aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
   }
 
 private:
   // These fields may only be accessed on the main thread
   DOMMediaStream* mStream;
 };
 
+class DOMMediaStream::PlaybackTrackListener : public MediaStreamTrackConsumer
+{
+public:
+  explicit PlaybackTrackListener(DOMMediaStream* aStream) :
+    mStream(aStream) {}
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PlaybackTrackListener,
+                                           MediaStreamTrackConsumer)
+
+  void NotifyEnded(MediaStreamTrack* aTrack) override
+  {
+    if (!mStream) {
+      MOZ_ASSERT(false);
+      return;
+    }
+
+    if (!aTrack) {
+      MOZ_ASSERT(false);
+      return;
+    }
+
+    MOZ_ASSERT(mStream->HasTrack(*aTrack));
+    mStream->NotifyTrackRemoved(aTrack);
+  }
+
+protected:
+  virtual ~PlaybackTrackListener() {}
+
+  RefPtr<DOMMediaStream> mStream;
+};
+
+NS_IMPL_ADDREF_INHERITED(DOMMediaStream::PlaybackTrackListener,
+                         MediaStreamTrackConsumer)
+NS_IMPL_RELEASE_INHERITED(DOMMediaStream::PlaybackTrackListener,
+                          MediaStreamTrackConsumer)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackConsumer)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener,
+                                   MediaStreamTrackConsumer,
+                                   mStream)
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMMediaStream,
                                                 DOMEventTargetHelper)
   tmp->Destroy();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwnedTracks)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTrackSourceGetter)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaybackTrackListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoPrincipal)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMMediaStream,
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwnedTracks)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTrackSourceGetter)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaybackTrackListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoPrincipal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(DOMMediaStream, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(DOMMediaStream, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream)
@@ -361,16 +368,17 @@ NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMe
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream)
 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
 
 DOMMediaStream::DOMMediaStream(nsPIDOMWindowInner* aWindow,
                                MediaStreamTrackSourceGetter* aTrackSourceGetter)
   : mLogicalStreamStartTime(0), mWindow(aWindow),
     mInputStream(nullptr), mOwnedStream(nullptr), mPlaybackStream(nullptr),
     mTracksPendingRemoval(0), mTrackSourceGetter(aTrackSourceGetter),
+    mPlaybackTrackListener(MakeAndAddRef<PlaybackTrackListener>(this)),
     mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false),
     mActive(false)
 {
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
 
   if (NS_SUCCEEDED(rv) && uuidgen) {
@@ -400,18 +408,22 @@ DOMMediaStream::Destroy()
   }
   if (mPlaybackListener) {
     mPlaybackListener->Forget();
     mPlaybackListener = nullptr;
   }
   for (const RefPtr<TrackPort>& info : mTracks) {
     // We must remove ourselves from each track's principal change observer list
     // before we die. CC may have cleared info->mTrack so guard against it.
-    if (info->GetTrack()) {
-      info->GetTrack()->RemovePrincipalChangeObserver(this);
+    MediaStreamTrack* track = info->GetTrack();
+    if (track) {
+      track->RemovePrincipalChangeObserver(this);
+      if (!track->Ended()) {
+        track->RemoveConsumer(mPlaybackTrackListener);
+      }
     }
   }
   if (mPlaybackPort) {
     mPlaybackPort->Destroy();
     mPlaybackPort = nullptr;
   }
   if (mOwnedPort) {
     mOwnedPort->Destroy();
@@ -1263,16 +1275,17 @@ DOMMediaStream::NotifyTrackAdded(const R
     }
   } else {
     LOG(LogLevel::Debug, ("DOMMediaStream %p saw a track get added. "
                           "Recomputing principal.", this));
     RecomputePrincipal();
   }
 
   aTrack->AddPrincipalChangeObserver(this);
+  aTrack->AddConsumer(mPlaybackTrackListener);
 
   for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
     mTrackListeners[i]->NotifyTrackAdded(aTrack);
   }
 
   if (mActive) {
     return;
   }
@@ -1292,16 +1305,17 @@ DOMMediaStream::NotifyTrackAdded(const R
   }
 }
 
 void
 DOMMediaStream::NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  aTrack->RemoveConsumer(mPlaybackTrackListener);
   aTrack->RemovePrincipalChangeObserver(this);
 
   for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
     mTrackListeners[i]->NotifyTrackRemoved(aTrack);
 
   }
 
   // Don't call RecomputePrincipal here as the track may still exist in the
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -630,16 +630,19 @@ protected:
                               const RefPtr<MediaStreamTrack>& aTrack);
 
   class OwnedStreamListener;
   friend class OwnedStreamListener;
 
   class PlaybackStreamListener;
   friend class PlaybackStreamListener;
 
+  class PlaybackTrackListener;
+  friend class PlaybackTrackListener;
+
   /**
    * Block a track in our playback stream. Calls NotifyPlaybackTrackBlocked()
    * after the MediaStreamGraph has applied the block and the track is no longer
    * live.
    */
   void BlockPlaybackTrack(TrackPort* aTrack);
 
   /**
@@ -699,16 +702,19 @@ protected:
   // Listener tracking changes to mOwnedStream. We use this to notify the
   // MediaStreamTracks we own about state changes.
   RefPtr<OwnedStreamListener> mOwnedListener;
 
   // Listener tracking changes to mPlaybackStream. This drives state changes
   // in this DOMMediaStream and notifications to mTrackListeners.
   RefPtr<PlaybackStreamListener> mPlaybackListener;
 
+  // Listener tracking when live MediaStreamTracks in mTracks end.
+  RefPtr<PlaybackTrackListener> mPlaybackTrackListener;
+
   nsTArray<nsAutoPtr<OnTracksAvailableCallback> > mRunOnTracksAvailable;
 
   // Set to true after MediaStreamGraph has created tracks for mPlaybackStream.
   bool mTracksCreated;
 
   nsString mID;
 
   // Keep these alive while the stream is alive.
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -48,16 +48,24 @@ MediaStreamTrackSource::ApplyConstraints
 {
   RefPtr<PledgeVoid> p = new PledgeVoid();
   p->Reject(new MediaStreamError(aWindow,
                                  NS_LITERAL_STRING("OverconstrainedError"),
                                  NS_LITERAL_STRING("")));
   return p.forget();
 }
 
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackConsumer)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackConsumer)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackConsumer)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTrackConsumer)
+
 /**
  * PrincipalHandleListener monitors changes in PrincipalHandle of the media flowing
  * through the MediaStreamGraph.
  *
  * When the main thread principal for a MediaStreamTrack changes, its principal
  * will be set to the combination of the previous principal and the new one.
  *
  * As a PrincipalHandle change later happens on the MediaStreamGraph thread, we will
@@ -166,25 +174,27 @@ MediaStreamTrack::Destroy()
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
                                                 DOMEventTargetHelper)
   tmp->Destroy();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumers)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningStream)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalTrack)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
                                                   DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumers)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwningStream)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalTrack)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
@@ -240,16 +250,18 @@ MediaStreamTrack::Stop()
 
   MOZ_ASSERT(mOwningStream, "Every MediaStreamTrack needs an owning DOMMediaStream");
   DOMMediaStream::TrackPort* port = mOwningStream->FindOwnedTrackPort(*this);
   MOZ_ASSERT(port, "A MediaStreamTrack must exist in its owning DOMMediaStream");
   RefPtr<Pledge<bool>> p = port->BlockSourceTrackId(mInputTrackID, BlockingMode::CREATION);
   Unused << p;
 
   mReadyState = MediaStreamTrackState::Ended;
+
+  NotifyEnded();
 }
 
 void
 MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints& aResult)
 {
   aResult = mConstraints;
 }
 
@@ -352,30 +364,55 @@ MediaStreamTrack::NotifyPrincipalHandleC
                        this, GetPrincipalFromHandle(handle),
                        mPrincipal.get(), mPendingPrincipal.get()));
   if (PrincipalHandleMatches(handle, mPendingPrincipal)) {
     SetPrincipal(mPendingPrincipal);
     mPendingPrincipal = nullptr;
   }
 }
 
+void
+MediaStreamTrack::NotifyEnded()
+{
+  MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);
+
+  for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) {
+    // Loop backwards by index in case the consumer removes itself in the
+    // callback.
+    mConsumers[i]->NotifyEnded(this);
+  }
+}
+
 bool
 MediaStreamTrack::AddPrincipalChangeObserver(
   PrincipalChangeObserver<MediaStreamTrack>* aObserver)
 {
   return mPrincipalChangeObservers.AppendElement(aObserver) != nullptr;
 }
 
 bool
 MediaStreamTrack::RemovePrincipalChangeObserver(
   PrincipalChangeObserver<MediaStreamTrack>* aObserver)
 {
   return mPrincipalChangeObservers.RemoveElement(aObserver);
 }
 
+void
+MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer* aConsumer)
+{
+  MOZ_ASSERT(!mConsumers.Contains(aConsumer));
+  mConsumers.AppendElement(aConsumer);
+}
+
+void
+MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer)
+{
+  mConsumers.RemoveElement(aConsumer);
+}
+
 already_AddRefed<MediaStreamTrack>
 MediaStreamTrack::Clone()
 {
   // MediaStreamTracks are currently governed by streams, so we need a dummy
   // DOMMediaStream to own our track clone. The dummy will never see any
   // dynamically created tracks (no input stream) so no need for a SourceGetter.
   RefPtr<DOMMediaStream> newStream =
     new DOMMediaStream(mOwningStream->GetParentObject(), nullptr);
@@ -418,16 +455,18 @@ MediaStreamTrack::OverrideEnded()
     MOZ_ASSERT(false);
     return;
   }
 
   mSource->UnregisterSink(this);
 
   mReadyState = MediaStreamTrackState::Ended;
 
+  NotifyEnded();
+
   DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
 }
 
 DOMMediaStream*
 MediaStreamTrack::GetInputDOMStream()
 {
   MediaStreamTrack* originalTrack =
     mOriginalTrack ? mOriginalTrack.get() : this;
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -226,16 +226,37 @@ public:
 
 protected:
   ~BasicUnstoppableTrackSource() {}
 
   const MediaSourceEnum mMediaSource;
 };
 
 /**
+ * Base class that consumers of a MediaStreamTrack can use to get notifications
+ * about state changes in the track.
+ */
+class MediaStreamTrackConsumer : public nsISupports
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(MediaStreamTrackConsumer)
+
+  /**
+   * Called when the track's readyState transitions to "ended".
+   * Unlike the "ended" event exposed to script this is called for any reason,
+   * including MediaStreamTrack::Stop().
+   */
+  virtual void NotifyEnded(MediaStreamTrack* aTrack) {};
+
+protected:
+  virtual ~MediaStreamTrackConsumer() {}
+};
+
+/**
  * Class representing a track in a DOMMediaStream.
  */
 class MediaStreamTrack : public DOMEventTargetHelper,
                          public MediaStreamTrackSource::Sink
 {
   // DOMMediaStream owns MediaStreamTrack instances, and requires access to
   // some internal state, e.g., GetInputStream(), GetOwnedStream().
   friend class mozilla::DOMMediaStream;
@@ -316,16 +337,22 @@ public:
   /**
    * Called by the PrincipalHandleListener when this track's PrincipalHandle changes on
    * the MediaStreamGraph thread. When the PrincipalHandle matches the pending
    * principal we know that the principal change has propagated to consumers.
    */
   void NotifyPrincipalHandleChanged(const PrincipalHandle& aPrincipalHandle);
 
   /**
+   * Called when this track's readyState transitions to "ended".
+   * Notifies all MediaStreamTrackConsumers that this track ended.
+   */
+  void NotifyEnded();
+
+  /**
    * Get this track's CORS mode.
    */
   CORSMode GetCORSMode() const { return GetSource().GetCORSMode(); }
 
   /**
    * Get this track's PeerIdentity.
    */
   const PeerIdentity* GetPeerIdentity() const { return GetSource().GetPeerIdentity(); }
@@ -359,16 +386,28 @@ public:
   /**
    * Remove an added PrincipalChangeObserver from this track.
    *
    * Returns true if it was successfully removed.
    */
   bool RemovePrincipalChangeObserver(PrincipalChangeObserver<MediaStreamTrack>* aObserver);
 
   /**
+   * Add a MediaStreamTrackConsumer to this track.
+   *
+   * Adding the same consumer multiple times is prohibited.
+   */
+  void AddConsumer(MediaStreamTrackConsumer* aConsumer);
+
+  /**
+   * Remove an added MediaStreamTrackConsumer from this track.
+   */
+  void RemoveConsumer(MediaStreamTrackConsumer* aConsumer);
+
+  /**
    * Adds a MediaStreamTrackListener to the MediaStreamGraph representation of
    * this track.
    */
   void AddListener(MediaStreamTrackListener* aListener);
 
   /**
    * Removes a MediaStreamTrackListener from the MediaStreamGraph representation
    * of this track.
@@ -424,16 +463,18 @@ protected:
    * source as this MediaStreamTrack.
    * aTrackID is the TrackID the new track will have in its owned stream.
    */
   virtual already_AddRefed<MediaStreamTrack> CloneInternal(DOMMediaStream* aOwningStream,
                                                            TrackID aTrackID) = 0;
 
   nsTArray<PrincipalChangeObserver<MediaStreamTrack>*> mPrincipalChangeObservers;
 
+  nsTArray<RefPtr<MediaStreamTrackConsumer>> mConsumers;
+
   RefPtr<DOMMediaStream> mOwningStream;
   TrackID mTrackID;
   TrackID mInputTrackID;
   RefPtr<MediaStreamTrackSource> mSource;
   RefPtr<MediaStreamTrack> mOriginalTrack;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIPrincipal> mPendingPrincipal;
   RefPtr<PrincipalHandleListener> mPrincipalHandleListener;