Bug 1161946 - MainThreadMediaStreamListener should be notified just when the stream is finished - patch 1, r=padenot
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 11 May 2015 15:07:24 +0100
changeset 243364 bad7af6079e93cdac37461f8e55ca0a7a528230b
parent 243363 6b1f47f5127108307a513220a0db57a011ed24a0
child 243365 4c5f304c7451b8e9e14baba2677ce54f95bdfd87
push id28738
push usercbook@mozilla.com
push dateTue, 12 May 2015 14:11:31 +0000
treeherdermozilla-central@bedce1b405a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs1161946
milestone40.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 1161946 - MainThreadMediaStreamListener should be notified just when the stream is finished - patch 1, r=padenot
dom/media/DOMMediaStream.cpp
dom/media/DOMMediaStream.h
dom/media/MediaStreamGraph.cpp
dom/media/MediaStreamGraph.h
dom/media/webaudio/AudioBufferSourceNode.cpp
dom/media/webaudio/AudioBufferSourceNode.h
dom/media/webaudio/AudioDestinationNode.cpp
dom/media/webaudio/AudioDestinationNode.h
dom/media/webaudio/OscillatorNode.cpp
dom/media/webaudio/OscillatorNode.h
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -465,21 +465,20 @@ DOMMediaStream::NotifyMediaStreamGraphSh
   // to prevent leaks.
   mNotifiedOfMediaStreamGraphShutdown = true;
   mRunOnTracksAvailable.Clear();
 
   mConsumersToKeepAlive.Clear();
 }
 
 void
-DOMMediaStream::NotifyStreamStateChanged()
+DOMMediaStream::NotifyStreamFinished()
 {
-  if (IsFinished()) {
-    mConsumersToKeepAlive.Clear();
-  }
+  MOZ_ASSERT(IsFinished());
+  mConsumersToKeepAlive.Clear();
 }
 
 void
 DOMMediaStream::OnTracksAvailable(OnTracksAvailableCallback* aRunnable)
 {
   if (mNotifiedOfMediaStreamGraphShutdown) {
     // No more tracks will ever be added, so just delete the callback now.
     delete aRunnable;
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -163,19 +163,19 @@ public:
 
   /**
    * Called when this stream's MediaStreamGraph has been shut down. Normally
    * MSGs are only shut down when all streams have been removed, so this
    * will only be called during a forced shutdown due to application exit.
    */
   void NotifyMediaStreamGraphShutdown();
   /**
-   * Called when the main-thread state of the MediaStream changed.
+   * Called when the main-thread state of the MediaStream goes to finished.
    */
-  void NotifyStreamStateChanged();
+  void NotifyStreamFinished();
 
   // Webrtc allows the remote side to name a stream whatever it wants, and we
   // need to surface this to content.
   void AssignId(const nsAString& aID) { mID = aID; }
 
   /**
    * Create an nsDOMMediaStream whose underlying stream is a SourceMediaStream.
    */
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -1538,21 +1538,24 @@ MediaStreamGraphImpl::ApplyStreamUpdate(
   mMonitor.AssertCurrentThreadOwns();
 
   MediaStream* stream = aUpdate->mStream;
   if (!stream)
     return;
   stream->mMainThreadCurrentTime = aUpdate->mNextMainThreadCurrentTime;
   stream->mMainThreadFinished = aUpdate->mNextMainThreadFinished;
 
-  if (stream->mWrapper) {
-    stream->mWrapper->NotifyStreamStateChanged();
-  }
-  for (int32_t i = stream->mMainThreadListeners.Length() - 1; i >= 0; --i) {
-    stream->mMainThreadListeners[i]->NotifyMainThreadStateChanged();
+  if (stream->ShouldNotifyStreamFinished()) {
+    if (stream->mWrapper) {
+      stream->mWrapper->NotifyStreamFinished();
+    }
+
+    for (int32_t i = stream->mMainThreadListeners.Length() - 1; i >= 0; --i) {
+      stream->mMainThreadListeners[i]->NotifyMainThreadStreamFinished();
+    }
   }
 }
 
 void
 MediaStreamGraphImpl::ForceShutDown()
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread");
   STREAM_LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p ForceShutdown", this));
@@ -1913,16 +1916,17 @@ MediaStream::MediaStream(DOMMediaStream*
   , mFinished(false)
   , mNotifiedFinished(false)
   , mNotifiedBlocked(false)
   , mHasCurrentData(false)
   , mNotifiedHasCurrentData(false)
   , mWrapper(aWrapper)
   , mMainThreadCurrentTime(0)
   , mMainThreadFinished(false)
+  , mFinishedNotificationSent(false)
   , mMainThreadDestroyed(false)
   , mGraph(nullptr)
   , mAudioChannelType(dom::AudioChannel::Normal)
 {
   MOZ_COUNT_CTOR(MediaStream);
   // aWrapper should not already be connected to a MediaStream! It needs
   // to be hooked up to this stream, and since this stream is only just
   // being created now, aWrapper must not be connected to anything.
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -221,17 +221,17 @@ public:
  * You should do something non-blocking and non-reentrant (e.g. dispatch an
  * event) and return. DispatchFromMainThreadAfterNextStreamStateUpdate
  * would be a good choice.
  * The listener is allowed to synchronously remove itself from the stream, but
  * not add or remove any other listeners.
  */
 class MainThreadMediaStreamListener {
 public:
-  virtual void NotifyMainThreadStateChanged() = 0;
+  virtual void NotifyMainThreadStreamFinished() = 0;
 };
 
 /**
  * Helper struct used to keep track of memory usage by AudioNodes.
  */
 struct AudioNodeSizes
 {
   AudioNodeSizes() : mDomNode(0), mStream(0), mEngine(0), mNodeType() {}
@@ -412,16 +412,17 @@ public:
     return mMainThreadCurrentTime;
   }
   // Return the main thread's view of whether this stream has finished.
   bool IsFinished()
   {
     NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
     return mMainThreadFinished;
   }
+
   bool IsDestroyed()
   {
     NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
     return mMainThreadDestroyed;
   }
 
   friend class MediaStreamGraphImpl;
   friend class MediaInputPort;
@@ -591,16 +592,27 @@ protected:
     mBufferStartTime += aBlockedTime;
     mGraphUpdateIndices.InsertTimeAtStart(aBlockedTime);
     mGraphUpdateIndices.AdvanceCurrentTime(aCurrentTime);
     mExplicitBlockerCount.AdvanceCurrentTime(aCurrentTime);
 
     mBuffer.ForgetUpTo(aCurrentTime - mBufferStartTime);
   }
 
+  bool ShouldNotifyStreamFinished()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
+    if (!mMainThreadFinished || mFinishedNotificationSent) {
+      return false;
+    }
+
+    mFinishedNotificationSent = true;
+    return true;
+  }
+
   // This state is all initialized on the main thread but
   // otherwise modified only on the media graph thread.
 
   // Buffered data. The start of the buffer corresponds to mBufferStartTime.
   // Conceptually the buffer contains everything this stream has ever played,
   // but we forget some prefix of the buffered data to bound the space usage.
   StreamBuffer mBuffer;
   // The time when the buffered data could be considered to have started playing.
@@ -691,16 +703,17 @@ protected:
   // True if this stream should be blocked in this phase.
   bool mBlockInThisPhase;
 
   // This state is only used on the main thread.
   DOMMediaStream* mWrapper;
   // Main-thread views of state
   StreamTime mMainThreadCurrentTime;
   bool mMainThreadFinished;
+  bool mFinishedNotificationSent;
   bool mMainThreadDestroyed;
 
   // Our media stream graph.  null if destroyed on the graph thread.
   MediaStreamGraphImpl* mGraph;
 
   dom::AudioChannel mAudioChannelType;
 };
 
--- a/dom/media/webaudio/AudioBufferSourceNode.cpp
+++ b/dom/media/webaudio/AudioBufferSourceNode.cpp
@@ -557,17 +557,16 @@ AudioBufferSourceNode::AudioBufferSource
               ChannelInterpretation::Speakers)
   , mLoopStart(0.0)
   , mLoopEnd(0.0)
   // mOffset and mDuration are initialized in Start().
   , mPlaybackRate(new AudioParam(this, SendPlaybackRateToStream, 1.0f, "playbackRate"))
   , mDetune(new AudioParam(this, SendDetuneToStream, 0.0f, "detune"))
   , mLoop(false)
   , mStartCalled(false)
-  , mStopped(false)
 {
   AudioBufferSourceNodeEngine* engine = new AudioBufferSourceNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::SOURCE_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*>(mStream.get()));
   mStream->AddMainThreadListener(this);
 }
 
 AudioBufferSourceNode::~AudioBufferSourceNode()
@@ -705,48 +704,45 @@ AudioBufferSourceNode::Stop(double aWhen
     // We've already stopped and had our stream shut down
     return;
   }
 
   ns->SetStreamTimeParameter(STOP, Context(), std::max(0.0, aWhen));
 }
 
 void
-AudioBufferSourceNode::NotifyMainThreadStateChanged()
+AudioBufferSourceNode::NotifyMainThreadStreamFinished()
 {
-  if (mStream->IsFinished()) {
-    class EndedEventDispatcher final : public nsRunnable
+  MOZ_ASSERT(mStream->IsFinished());
+
+  class EndedEventDispatcher final : public nsRunnable
+  {
+  public:
+    explicit EndedEventDispatcher(AudioBufferSourceNode* aNode)
+      : mNode(aNode) {}
+    NS_IMETHODIMP Run() override
     {
-    public:
-      explicit EndedEventDispatcher(AudioBufferSourceNode* aNode)
-        : mNode(aNode) {}
-      NS_IMETHODIMP Run() override
-      {
-        // If it's not safe to run scripts right now, schedule this to run later
-        if (!nsContentUtils::IsSafeToRunScript()) {
-          nsContentUtils::AddScriptRunner(this);
-          return NS_OK;
-        }
-
-        mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
+      // If it's not safe to run scripts right now, schedule this to run later
+      if (!nsContentUtils::IsSafeToRunScript()) {
+        nsContentUtils::AddScriptRunner(this);
         return NS_OK;
       }
-    private:
-      nsRefPtr<AudioBufferSourceNode> mNode;
-    };
-    if (!mStopped) {
-      // Only dispatch the ended event once
-      NS_DispatchToMainThread(new EndedEventDispatcher(this));
-      mStopped = true;
+
+      mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
+      return NS_OK;
     }
+  private:
+    nsRefPtr<AudioBufferSourceNode> mNode;
+  };
 
-    // Drop the playing reference
-    // Warning: The below line might delete this.
-    MarkInactive();
-  }
+  NS_DispatchToMainThread(new EndedEventDispatcher(this));
+
+  // Drop the playing reference
+  // Warning: The below line might delete this.
+  MarkInactive();
 }
 
 void
 AudioBufferSourceNode::SendPlaybackRateToStream(AudioNode* aNode)
 {
   AudioBufferSourceNode* This = static_cast<AudioBufferSourceNode*>(aNode);
   SendTimelineParameterToStream(This, PLAYBACKRATE, *This->mPlaybackRate);
 }
--- a/dom/media/webaudio/AudioBufferSourceNode.h
+++ b/dom/media/webaudio/AudioBufferSourceNode.h
@@ -89,17 +89,17 @@ public:
   {
     mLoopEnd = aEnd;
     SendLoopParametersToStream();
   }
   void SendDopplerShiftToStream(double aDopplerShift);
 
   IMPL_EVENT_HANDLER(ended)
 
-  virtual void NotifyMainThreadStateChanged() override;
+  virtual void NotifyMainThreadStreamFinished() override;
 
   virtual const char* NodeType() const override
   {
     return "AudioBufferSourceNode";
   }
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
@@ -142,16 +142,15 @@ private:
   double mLoopEnd;
   double mOffset;
   double mDuration;
   nsRefPtr<AudioBuffer> mBuffer;
   nsRefPtr<AudioParam> mPlaybackRate;
   nsRefPtr<AudioParam> mDetune;
   bool mLoop;
   bool mStartCalled;
-  bool mStopped;
 };
 
 }
 }
 
 #endif
 
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -346,17 +346,16 @@ AudioDestinationNode::AudioDestinationNo
                                            float aSampleRate)
   : AudioNode(aContext,
               aIsOffline ? aNumberOfChannels : 2,
               ChannelCountMode::Explicit,
               ChannelInterpretation::Speakers)
   , mFramesToProduce(aLength)
   , mAudioChannel(AudioChannel::Normal)
   , mIsOffline(aIsOffline)
-  , mHasFinished(false)
   , mAudioChannelAgentPlaying(false)
   , mExtraCurrentTime(0)
   , mExtraCurrentTimeSinceLastStartedBlocking(0)
   , mExtraCurrentTimeUpdatedSinceLastStableState(false)
 {
   bool startWithAudioDriver = true;
   MediaStreamGraph* graph = aIsOffline ?
                             MediaStreamGraph::CreateNonRealtimeInstance(aSampleRate) :
@@ -421,25 +420,24 @@ AudioDestinationNode::DestroyMediaStream
   MediaStreamGraph* graph = mStream->Graph();
   if (graph->IsNonRealtime()) {
     MediaStreamGraph::DestroyNonRealtimeInstance(graph);
   }
   AudioNode::DestroyMediaStream();
 }
 
 void
-AudioDestinationNode::NotifyMainThreadStateChanged()
+AudioDestinationNode::NotifyMainThreadStreamFinished()
 {
-  if (mStream->IsFinished() && !mHasFinished) {
-    mHasFinished = true;
-    if (mIsOffline) {
-      nsCOMPtr<nsIRunnable> runnable =
-        NS_NewRunnableMethod(this, &AudioDestinationNode::FireOfflineCompletionEvent);
-      NS_DispatchToCurrentThread(runnable);
-    }
+  MOZ_ASSERT(mStream->IsFinished());
+
+  if (mIsOffline) {
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableMethod(this, &AudioDestinationNode::FireOfflineCompletionEvent);
+    NS_DispatchToCurrentThread(runnable);
   }
 }
 
 void
 AudioDestinationNode::FireOfflineCompletionEvent()
 {
   AudioNodeStream* stream = static_cast<AudioNodeStream*>(Stream());
   OfflineDestinationNodeEngine* engine =
--- a/dom/media/webaudio/AudioDestinationNode.h
+++ b/dom/media/webaudio/AudioDestinationNode.h
@@ -59,17 +59,17 @@ public:
   void OfflineShutdown();
 
   // nsIDOMEventListener - by proxy
   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override;
 
   AudioChannel MozAudioChannelType() const;
   void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
 
-  virtual void NotifyMainThreadStateChanged() override;
+  virtual void NotifyMainThreadStreamFinished() override;
   void FireOfflineCompletionEvent();
 
   // An amount that should be added to the MediaStream's current time to
   // get the AudioContext.currentTime.
   double ExtraCurrentTime();
 
   // When aIsOnlyNode is true, this is the only node for the AudioContext.
   void SetIsOnlyNodeForContext(bool aIsOnlyNode);
@@ -104,17 +104,16 @@ private:
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 
   nsRefPtr<EventProxyHandler> mEventProxyHelper;
   nsRefPtr<Promise> mOfflineRenderingPromise;
 
   // Audio Channel Type.
   AudioChannel mAudioChannel;
   bool mIsOffline;
-  bool mHasFinished;
   bool mAudioChannelAgentPlaying;
 
   TimeStamp mStartedBlockingDueToBeingOnlyNode;
   double mExtraCurrentTime;
   double mExtraCurrentTimeSinceLastStartedBlocking;
   bool mExtraCurrentTimeUpdatedSinceLastStableState;
 };
 
--- a/dom/media/webaudio/OscillatorNode.cpp
+++ b/dom/media/webaudio/OscillatorNode.cpp
@@ -379,17 +379,16 @@ OscillatorNode::OscillatorNode(AudioCont
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mType(OscillatorType::Sine)
   , mFrequency(new AudioParam(this, SendFrequencyToStream, 440.0f, "frequency"))
   , mDetune(new AudioParam(this, SendDetuneToStream, 0.0f, "detune"))
   , mStartCalled(false)
-  , mStopped(false)
 {
   OscillatorNodeEngine* engine = new OscillatorNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::SOURCE_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
   mStream->AddMainThreadListener(this);
 }
 
 OscillatorNode::~OscillatorNode()
@@ -507,44 +506,41 @@ OscillatorNode::Stop(double aWhen, Error
   }
 
   // TODO: Perhaps we need to do more here.
   ns->SetStreamTimeParameter(OscillatorNodeEngine::STOP,
                              Context(), std::max(0.0, aWhen));
 }
 
 void
-OscillatorNode::NotifyMainThreadStateChanged()
+OscillatorNode::NotifyMainThreadStreamFinished()
 {
-  if (mStream->IsFinished()) {
-    class EndedEventDispatcher final : public nsRunnable
+  MOZ_ASSERT(mStream->IsFinished());
+
+  class EndedEventDispatcher final : public nsRunnable
+  {
+  public:
+    explicit EndedEventDispatcher(OscillatorNode* aNode)
+      : mNode(aNode) {}
+    NS_IMETHOD Run() override
     {
-    public:
-      explicit EndedEventDispatcher(OscillatorNode* aNode)
-        : mNode(aNode) {}
-      NS_IMETHOD Run() override
-      {
-        // If it's not safe to run scripts right now, schedule this to run later
-        if (!nsContentUtils::IsSafeToRunScript()) {
-          nsContentUtils::AddScriptRunner(this);
-          return NS_OK;
-        }
-
-        mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
+      // If it's not safe to run scripts right now, schedule this to run later
+      if (!nsContentUtils::IsSafeToRunScript()) {
+        nsContentUtils::AddScriptRunner(this);
         return NS_OK;
       }
-    private:
-      nsRefPtr<OscillatorNode> mNode;
-    };
-    if (!mStopped) {
-      // Only dispatch the ended event once
-      NS_DispatchToMainThread(new EndedEventDispatcher(this));
-      mStopped = true;
+
+      mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
+      return NS_OK;
     }
+  private:
+    nsRefPtr<OscillatorNode> mNode;
+  };
 
-    // Drop the playing reference
-    // Warning: The below line might delete this.
-    MarkInactive();
-  }
+  NS_DispatchToMainThread(new EndedEventDispatcher(this));
+
+  // Drop the playing reference
+  // Warning: The below line might delete this.
+  MarkInactive();
 }
 
 }
 }
--- a/dom/media/webaudio/OscillatorNode.h
+++ b/dom/media/webaudio/OscillatorNode.h
@@ -73,17 +73,17 @@ public:
     mPeriodicWave = &aPeriodicWave;
     // SendTypeToStream will call SendPeriodicWaveToStream for us.
     mType = OscillatorType::Custom;
     SendTypeToStream();
   }
 
   IMPL_EVENT_HANDLER(ended)
 
-  virtual void NotifyMainThreadStateChanged() override;
+  virtual void NotifyMainThreadStreamFinished() override;
 
   virtual const char* NodeType() const override
   {
     return "OscillatorNode";
   }
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
@@ -98,16 +98,15 @@ private:
   void SendPeriodicWaveToStream();
 
 private:
   OscillatorType mType;
   nsRefPtr<PeriodicWave> mPeriodicWave;
   nsRefPtr<AudioParam> mFrequency;
   nsRefPtr<AudioParam> mDetune;
   bool mStartCalled;
-  bool mStopped;
 };
 
 }
 }
 
 #endif