Bug 850587. Part 2: Make NotifyHasCurrentData fire when a stream would be able to produce data if it was not blocked. r=jesup,a=bajaj
authorRobert O'Callahan <robert@ocallahan.org>
Thu, 21 Mar 2013 00:19:39 +1300
changeset 132463 28a6a1823441033679b884aeb78f49a92e44d743
parent 132462 3302b3b38631f2cc0700a32a839790f16da1f1a6
child 132464 286f3ec182e8ca48fcfa8f4e08adc27d68cb0a41
push id2323
push userbbajaj@mozilla.com
push dateMon, 01 Apr 2013 19:47:02 +0000
treeherdermozilla-beta@7712be144d91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup, bajaj
bugs850587
milestone21.0a2
Bug 850587. Part 2: Make NotifyHasCurrentData fire when a stream would be able to produce data if it was not blocked. r=jesup,a=bajaj Also removes NotifyHasCurrentData's boolean parameter; we just fire this once and treat a stream that has once had current data as always having current data (since we block a stream that would advance beyond its available data).
content/html/content/src/nsHTMLMediaElement.cpp
content/media/AudioNodeStream.h
content/media/MediaStreamGraph.cpp
content/media/MediaStreamGraph.h
content/media/TrackUnionStream.h
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -2410,18 +2410,17 @@ nsresult nsHTMLMediaElement::FinishDecod
 
 class nsHTMLMediaElement::StreamListener : public MediaStreamListener {
 public:
   StreamListener(nsHTMLMediaElement* aElement) :
     mElement(aElement),
     mHaveCurrentData(false),
     mBlocked(false),
     mMutex("nsHTMLMediaElement::StreamListener"),
-    mPendingNotifyOutput(false),
-    mDidHaveCurrentData(false)
+    mPendingNotifyOutput(false)
   {}
   void Forget() { mElement = nullptr; }
 
   // Main thread
   void DoNotifyFinished()
   {
     if (mElement) {
       mElement->PlaybackEnded();
@@ -2478,31 +2477,22 @@ public:
     aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
   virtual void NotifyFinished(MediaStreamGraph* aGraph)
   {
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(this, &StreamListener::DoNotifyFinished);
     aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
-  virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph,
-                                    bool aHasCurrentData)
+  virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph)
   {
     MutexAutoLock lock(mMutex);
-    if (mDidHaveCurrentData == aHasCurrentData)
-      return;
-    mDidHaveCurrentData = aHasCurrentData;
-    // Ignore the case where aHasCurrentData is false. If aHasCurrentData
-    // changes from true to false, we don't worry about it. Video elements
-    // preserve the last played frame anyway.
-    if (aHasCurrentData) {
-      nsCOMPtr<nsIRunnable> event =
-        NS_NewRunnableMethod(this, &StreamListener::DoNotifyHaveCurrentData);
-      aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
-    }
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(this, &StreamListener::DoNotifyHaveCurrentData);
+    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
   virtual void NotifyOutput(MediaStreamGraph* aGraph)
   {
     MutexAutoLock lock(mMutex);
     if (mPendingNotifyOutput)
       return;
     mPendingNotifyOutput = true;
     nsCOMPtr<nsIRunnable> event =
@@ -2514,17 +2504,16 @@ private:
   // These fields may only be accessed on the main thread
   nsHTMLMediaElement* mElement;
   bool mHaveCurrentData;
   bool mBlocked;
 
   // mMutex protects the fields below; they can be accessed on any thread
   Mutex mMutex;
   bool mPendingNotifyOutput;
-  bool mDidHaveCurrentData;
 };
 
 void nsHTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream)
 {
   NS_ASSERTION(!mSrcStream && !mSrcStreamListener, "Should have been ended already");
 
   mSrcStream = aStream;
   // XXX if we ever support capturing the output of a media element which is
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -36,16 +36,18 @@ public:
   enum { AUDIO_TRACK = 1 };
 
   /**
    * Transfers ownership of aEngine to the new AudioNodeStream.
    */
   explicit AudioNodeStream(AudioNodeEngine* aEngine)
     : ProcessedMediaStream(nullptr), mEngine(aEngine), mLastChunk(nullptr)
   {
+    // AudioNodes are always producing data
+    mHasCurrentData = true;
   }
   ~AudioNodeStream();
 
   // Control API
   /**
    * Sets a parameter that's a time relative to some stream's played time.
    * This time is converted to a time relative to this stream when it's set.
    */
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -162,16 +162,19 @@ MediaStreamGraphImpl::ExtractPendingInpu
       }
       if (data->mCommands & SourceMediaStream::TRACK_END) {
         aStream->mBuffer.FindTrack(data->mID)->SetEnded();
         aStream->mUpdateTracks.RemoveElementAt(i);
       }
     }
     aStream->mBuffer.AdvanceKnownTracksTime(aStream->mUpdateKnownTracksTime);
   }
+  if (aStream->mBuffer.GetEnd() > 0) {
+    aStream->mHasCurrentData = true;
+  }
   if (finished) {
     FinishStream(aStream);
   }
 }
 
 void
 MediaStreamGraphImpl::UpdateBufferSufficiencyState(SourceMediaStream* aStream)
 {
@@ -637,20 +640,22 @@ MediaStreamGraphImpl::RecomputeBlockingA
     MediaStream* stream = aStreams[i];
     stream->mBlocked.SetAtAndAfter(aTime, stream->mBlockInThisPhase);
   }
 }
 
 void
 MediaStreamGraphImpl::NotifyHasCurrentData(MediaStream* aStream)
 {
-  for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) {
-    MediaStreamListener* l = aStream->mListeners[j];
-    l->NotifyHasCurrentData(this,
-      GraphTimeToStreamTime(aStream, mCurrentTime) < aStream->mBuffer.GetEnd());
+  if (!aStream->mNotifiedHasCurrentData && aStream->mHasCurrentData) {
+    for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) {
+      MediaStreamListener* l = aStream->mListeners[j];
+      l->NotifyHasCurrentData(this);
+    }
+    aStream->mNotifiedHasCurrentData = true;
   }
 }
 
 void
 MediaStreamGraphImpl::CreateOrDestroyAudioStreams(GraphTime aAudioOutputStartTime,
                                                   MediaStream* aStream)
 {
   nsAutoTArray<bool,2> audioOutputStreamsFound;
@@ -1555,16 +1560,19 @@ void
 MediaStream::AddListenerImpl(already_AddRefed<MediaStreamListener> aListener)
 {
   MediaStreamListener* listener = *mListeners.AppendElement() = aListener;
   listener->NotifyBlockingChanged(GraphImpl(),
     mNotifiedBlocked ? MediaStreamListener::BLOCKED : MediaStreamListener::UNBLOCKED);
   if (mNotifiedFinished) {
     listener->NotifyFinished(GraphImpl());
   }
+  if (mNotifiedHasCurrentData) {
+    listener->NotifyHasCurrentData(GraphImpl());
+  }
 }
 
 void
 MediaStream::AddListener(MediaStreamListener* aListener)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream, MediaStreamListener* aListener) :
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -123,20 +123,22 @@ public:
   };
   /**
    * Notify that the blocking status of the stream changed. The initial state
    * is assumed to be BLOCKED.
    */
   virtual void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked) {}
 
   /**
-   * Notify that the stream has (or does not have) data in each track
-   * for the stream's current time.
+   * Notify that the stream has data in each track
+   * for the stream's current time. Once this state becomes true, it will
+   * always be true since we block stream time from progressing to times where
+   * there isn't data in each track.
    */
-  virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph, bool aHasCurrentData) {}
+  virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph) {}
 
   /**
    * Notify that the stream output is advancing.
    */
   virtual void NotifyOutput(MediaStreamGraph* aGraph) {}
 
   /**
    * Notify that the stream finished.
@@ -263,16 +265,18 @@ public:
   MediaStream(DOMMediaStream* aWrapper)
     : mBufferStartTime(0)
     , mExplicitBlockerCount(0)
     , mBlocked(false)
     , mGraphUpdateIndices(0)
     , mFinished(false)
     , mNotifiedFinished(false)
     , mNotifiedBlocked(false)
+    , mHasCurrentData(false)
+    , mNotifiedHasCurrentData(false)
     , mWrapper(aWrapper)
     , mMainThreadCurrentTime(0)
     , mMainThreadFinished(false)
     , mMainThreadDestroyed(false)
     , mGraph(nullptr)
   {
   }
   virtual ~MediaStream()
@@ -413,16 +417,18 @@ public:
    * Convert stream time to graph time. The result can be > mStateComputedTime,
    * in which case we did the conversion optimistically assuming the stream
    * will not be blocked after mStateComputedTime.
    */
   GraphTime StreamTimeToGraphTime(StreamTime aTime);
   bool IsFinishedOnGraphThread() { return mFinished; }
   void FinishOnGraphThread();
 
+  bool HasCurrentData() { return mHasCurrentData; }
+
 protected:
   virtual void AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime, GraphTime aBlockedTime)
   {
     mBufferStartTime += aBlockedTime;
     mGraphUpdateIndices.InsertTimeAtStart(aBlockedTime);
     mGraphUpdateIndices.AdvanceCurrentTime(aCurrentTime);
     mExplicitBlockerCount.AdvanceCurrentTime(aCurrentTime);
 
@@ -495,16 +501,27 @@ protected:
    * and fired NotifyFinished notifications.
    */
   bool mNotifiedFinished;
   /**
    * When true, the last NotifyBlockingChanged delivered to the listeners
    * indicated that the stream is blocked.
    */
   bool mNotifiedBlocked;
+  /**
+   * True if some data can be present by this stream if/when it's unblocked.
+   * Set by the stream itself on the MediaStreamGraph thread. Only changes
+   * from false to true once a stream has data, since we won't
+   * unblock it until there's more data.
+   */
+  bool mHasCurrentData;
+  /**
+   * True if mHasCurrentData is true and we've notified listeners.
+   */
+  bool mNotifiedHasCurrentData;
 
   // Temporary data for ordering streams by dependency graph
   bool mHasBeenOrdered;
   bool mIsOnOrderingStack;
   // True if the stream is being consumed (i.e. has track data being played,
   // or is feeding into some stream that is being consumed).
   bool mIsConsumed;
   // Temporary data for computing blocking status of streams
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.h
@@ -42,21 +42,25 @@ public:
   {
     nsAutoTArray<bool,8> mappedTracksFinished;
     nsAutoTArray<bool,8> mappedTracksWithMatchingInputTracks;
     for (uint32_t i = 0; i < mTrackMap.Length(); ++i) {
       mappedTracksFinished.AppendElement(true);
       mappedTracksWithMatchingInputTracks.AppendElement(false);
     }
     bool allFinished = true;
+    bool allHaveCurrentData = true;
     for (uint32_t i = 0; i < mInputs.Length(); ++i) {
       MediaStream* stream = mInputs[i]->GetSource();
       if (!stream->IsFinishedOnGraphThread()) {
         allFinished = false;
       }
+      if (!stream->HasCurrentData()) {
+        allHaveCurrentData = false;
+      }
       for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer());
            !tracks.IsEnded(); tracks.Next()) {
         bool found = false;
         for (uint32_t j = 0; j < mTrackMap.Length(); ++j) {
           TrackMapEntry* map = &mTrackMap[j];
           if (map->mInputPort == mInputs[i] && map->mInputTrackID == tracks->GetID()) {
             bool trackFinished;
             StreamBuffer::Track* outputTrack = mBuffer.FindTrack(map->mOutputTrackID);
@@ -92,16 +96,20 @@ public:
     }
     if (allFinished && mAutofinish) {
       // All streams have finished and won't add any more tracks, and
       // all our tracks have actually finished and been removed from our map,
       // so we're finished now.
       FinishOnGraphThread();
     }
     mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime(aTo));
+    if (allHaveCurrentData) {
+      // We can make progress if we're not blocked
+      mHasCurrentData = true;
+    }
   }
 
 protected:
   // Only non-ended tracks are allowed to persist in this map.
   struct TrackMapEntry {
     MediaInputPort* mInputPort;
     // We keep track IDs instead of track pointers because
     // tracks can be removed without us being notified (e.g.