Bug 943461. Part 8: When a MediaDecoder is decoding to a stream, run PlaybackEnded when the stream finishes. r=padenot
authorRobert O'Callahan <robert@ocallahan.org>
Sat, 07 Dec 2013 01:01:33 +1300
changeset 162325 9811231a9449a9aba1db0cc191f6e3eecdb27035
parent 162324 249b7a7cec009fbb2c2c14bf1b76af5f86416520
child 162326 4378e7bf2afcb67d47157c465666215b53ee6e12
push idunknown
push userunknown
push dateunknown
reviewerspadenot
bugs943461
milestone29.0a1
Bug 943461. Part 8: When a MediaDecoder is decoding to a stream, run PlaybackEnded when the stream finishes. r=padenot
content/media/MediaDecoder.cpp
content/media/MediaDecoder.h
content/media/MediaDecoderStateMachine.cpp
content/media/TrackUnionStream.h
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -195,55 +195,69 @@ MediaDecoder::DecodedStreamData::Decoded
     mStreamInitialized(false),
     mHaveSentFinish(false),
     mHaveSentFinishAudio(false),
     mHaveSentFinishVideo(false),
     mStream(aStream),
     mHaveBlockedForPlayState(false),
     mHaveBlockedForStateMachineNotPlaying(false)
 {
-  mStream->AddMainThreadListener(this);
-  mListener = new DecodedStreamGraphListener(mStream);
+  mListener = new DecodedStreamGraphListener(mStream, this);
   mStream->AddListener(mListener);
 }
 
 MediaDecoder::DecodedStreamData::~DecodedStreamData()
 {
-  mStream->RemoveMainThreadListener(this);
   mListener->Forget();
   mStream->Destroy();
 }
 
-void
-MediaDecoder::DecodedStreamData::NotifyMainThreadStateChanged()
-{
-  mDecoder->NotifyDecodedStreamMainThreadStateChanged();
-  if (mStream->IsFinished()) {
-    mListener->SetFinishedOnMainThread(true);
-  }
-}
-
-MediaDecoder::DecodedStreamGraphListener::DecodedStreamGraphListener(MediaStream* aStream)
-  : mMutex("MediaDecoder::DecodedStreamData::mMutex"),
+MediaDecoder::DecodedStreamGraphListener::DecodedStreamGraphListener(MediaStream* aStream,
+                                                                     DecodedStreamData* aData)
+  : mData(aData),
+    mMutex("MediaDecoder::DecodedStreamData::mMutex"),
     mStream(aStream),
     mLastOutputTime(aStream->GetCurrentTime()),
     mStreamFinishedOnMainThread(false)
 {
 }
 
 void
 MediaDecoder::DecodedStreamGraphListener::NotifyOutput(MediaStreamGraph* aGraph,
                                                        GraphTime aCurrentTime)
 {
   MutexAutoLock lock(mMutex);
   if (mStream) {
     mLastOutputTime = mStream->GraphTimeToStreamTime(aCurrentTime);
   }
 }
 
+void
+MediaDecoder::DecodedStreamGraphListener::DoNotifyFinished()
+{
+  if (mData && mData->mDecoder) {
+    if (mData->mDecoder->GetState() == PLAY_STATE_PLAYING) {
+      nsCOMPtr<nsIRunnable> event =
+        NS_NewRunnableMethod(mData->mDecoder, &MediaDecoder::PlaybackEnded);
+      NS_DispatchToCurrentThread(event);
+    }
+  }
+
+  MutexAutoLock lock(mMutex);
+  mStreamFinishedOnMainThread = true;
+}
+
+void
+MediaDecoder::DecodedStreamGraphListener::NotifyFinished(MediaStreamGraph* aGraph)
+{
+  nsCOMPtr<nsIRunnable> event =
+    NS_NewRunnableMethod(this, &DecodedStreamGraphListener::DoNotifyFinished);
+  aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+}
+
 void MediaDecoder::DestroyDecodedStream()
 {
   MOZ_ASSERT(NS_IsMainThread());
   GetReentrantMonitor().AssertCurrentThreadIn();
 
   // All streams are having their SourceMediaStream disconnected, so they
   // need to be explicitly blocked again.
   for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
@@ -321,29 +335,16 @@ void MediaDecoder::RecreateDecodedStream
   UpdateStreamBlockingForStateMachinePlaying();
 
   mDecodedStream->mHaveBlockedForPlayState = mPlayState != PLAY_STATE_PLAYING;
   if (mDecodedStream->mHaveBlockedForPlayState) {
     mDecodedStream->mStream->ChangeExplicitBlockerCount(1);
   }
 }
 
-void MediaDecoder::NotifyDecodedStreamMainThreadStateChanged()
-{
-  if (mTriggerPlaybackEndedWhenSourceStreamFinishes && mDecodedStream &&
-      mDecodedStream->mStream->IsFinished()) {
-    mTriggerPlaybackEndedWhenSourceStreamFinishes = false;
-    if (GetState() == PLAY_STATE_PLAYING) {
-      nsCOMPtr<nsIRunnable> event =
-        NS_NewRunnableMethod(this, &MediaDecoder::PlaybackEnded);
-      NS_DispatchToCurrentThread(event);
-    }
-  }
-}
-
 void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream,
                                    bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoder::AddOutputStream this=%p aStream=%p!",
                              this, aStream));
 
   {
@@ -416,17 +417,16 @@ MediaDecoder::MediaDecoder() :
   mIsDormant(false),
   mIsExitingDormant(false),
   mPlayState(PLAY_STATE_PAUSED),
   mNextState(PLAY_STATE_PAUSED),
   mRequestedSeekTime(-1.0),
   mCalledResourceLoaded(false),
   mIgnoreProgressData(false),
   mInfiniteStream(false),
-  mTriggerPlaybackEndedWhenSourceStreamFinishes(false),
   mOwner(nullptr),
   mFrameBufferLength(0),
   mPinnedForSeek(false),
   mShuttingDown(false),
   mPausedForPlaybackRateNull(false),
   mAudioChannelType(AUDIO_CHANNEL_NORMAL)
 {
   MOZ_COUNT_CTOR(MediaDecoder);
@@ -931,22 +931,16 @@ void MediaDecoder::PlaybackEnded()
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown || mPlayState == MediaDecoder::PLAY_STATE_SEEKING)
     return;
 
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
 
-    if (mDecodedStream && !mDecodedStream->mStream->IsFinished()) {
-      // Wait for it to finish before firing PlaybackEnded()
-      mTriggerPlaybackEndedWhenSourceStreamFinishes = true;
-      return;
-    }
-
     for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
       OutputStreamData& os = mOutputStreams[i];
       if (os.mStream->IsDestroyed()) {
         // Probably the DOM MediaStream was GCed. Clean up.
         os.mPort->Destroy();
         mOutputStreams.RemoveElementAt(i);
         continue;
       }
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -345,23 +345,21 @@ public:
 
   // All MediaStream-related data is protected by mReentrantMonitor.
   // We have at most one DecodedStreamData per MediaDecoder. Its stream
   // is used as the input for each ProcessedMediaStream created by calls to
   // captureStream(UntilEnded). Seeking creates a new source stream, as does
   // replaying after the input as ended. In the latter case, the new source is
   // not connected to streams created by captureStreamUntilEnded.
 
-  struct DecodedStreamData MOZ_FINAL : public MainThreadMediaStreamListener {
+  struct DecodedStreamData {
     DecodedStreamData(MediaDecoder* aDecoder,
                       int64_t aInitialTime, SourceMediaStream* aStream);
     ~DecodedStreamData();
 
-    virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
-
     StreamTime GetLastOutputTime() { return mListener->GetLastOutputTime(); }
     bool IsFinished() { return mListener->IsFinishedOnMainThread(); }
 
     // The following group of fields are protected by the decoder's monitor
     // and can be read or written on any thread.
     int64_t mLastAudioPacketTime; // microseconds
     int64_t mLastAudioPacketEndTime; // microseconds
     // Count of audio frames written to the stream
@@ -395,40 +393,51 @@ public:
     bool mHaveBlockedForPlayState;
     // We also have an explicit blocker on the stream when
     // mDecoderStateMachine is non-null and MediaDecoderStateMachine is false.
     bool mHaveBlockedForStateMachineNotPlaying;
   };
 
   class DecodedStreamGraphListener : public MediaStreamListener {
   public:
-    DecodedStreamGraphListener(MediaStream* aStream);
+    DecodedStreamGraphListener(MediaStream* aStream, DecodedStreamData* aData);
     virtual void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) MOZ_OVERRIDE;
+    virtual void NotifyFinished(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
+
+    void DoNotifyFinished();
 
     StreamTime GetLastOutputTime()
     {
       MutexAutoLock lock(mMutex);
       return mLastOutputTime;
     }
     void Forget()
     {
+      NS_ASSERTION(NS_IsMainThread(), "Main thread only");
+      mData = nullptr;
+
       MutexAutoLock lock(mMutex);
       mStream = nullptr;
     }
-    void SetFinishedOnMainThread(bool aFinished)
+    bool SetFinishedOnMainThread(bool aFinished)
     {
       MutexAutoLock lock(mMutex);
+      bool result = !mStreamFinishedOnMainThread;
       mStreamFinishedOnMainThread = aFinished;
+      return result;
     }
     bool IsFinishedOnMainThread()
     {
       MutexAutoLock lock(mMutex);
       return mStreamFinishedOnMainThread;
     }
   private:
+    // Main thread only
+    DecodedStreamData* mData;
+
     Mutex mMutex;
     // Protected by mMutex
     nsRefPtr<MediaStream> mStream;
     // Protected by mMutex
     StreamTime mLastOutputTime;
     // Protected by mMutex
     bool mStreamFinishedOnMainThread;
   };
@@ -460,22 +469,16 @@ public:
    * Decoder monitor must be held.
    */
   void RecreateDecodedStream(int64_t aStartTimeUSecs);
   /**
    * Call this when mDecoderStateMachine or mDecoderStateMachine->IsPlaying() changes.
    * Decoder monitor must be held.
    */
   void UpdateStreamBlockingForStateMachinePlaying();
-  /**
-   * Called when the state of mDecodedStream as visible on the main thread
-   * has changed. In particular we want to know when the stream has finished
-   * so we can call PlaybackEnded.
-   */
-  void NotifyDecodedStreamMainThreadStateChanged();
   nsTArray<OutputStreamData>& OutputStreams()
   {
     GetReentrantMonitor().AssertCurrentThreadIn();
     return mOutputStreams;
   }
   DecodedStreamData* GetDecodedStream()
   {
     GetReentrantMonitor().AssertCurrentThreadIn();
@@ -1129,20 +1132,16 @@ protected:
   // during seek and duration operations to prevent the progress indicator
   // from jumping around. Read/Write from any thread. Must have decode monitor
   // locked before accessing.
   bool mIgnoreProgressData;
 
   // True if the stream is infinite (e.g. a webradio).
   bool mInfiniteStream;
 
-  // True if NotifyDecodedStreamMainThreadStateChanged should retrigger
-  // PlaybackEnded when mDecodedStream->mStream finishes.
-  bool mTriggerPlaybackEndedWhenSourceStreamFinishes;
-
   // Start timer to update download progress information.
   nsresult StartProgress();
 
   // Stop progress information timer.
   nsresult StopProgress();
 
   // Ensures our media stream has been pinned.
   void PinForSeek();
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -2364,20 +2364,24 @@ nsresult MediaDecoderStateMachine::RunSt
       if (mState != DECODER_STATE_COMPLETED) {
         // While we're presenting a frame we can change state. Whatever changed
         // our state should have scheduled another state machine run.
         NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
         return NS_OK;
       }
 
       StopAudioThread();
-      if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING) {
+      // When we're decoding to a stream, the stream's main-thread finish signal
+      // will take care of calling MediaDecoder::PlaybackEnded.
+      if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING &&
+          !mDecoder->GetDecodedStream()) {
         int64_t videoTime = HasVideo() ? mVideoFrameEndTime : 0;
         int64_t clockTime = std::max(mEndTime, std::max(videoTime, GetAudioClock()));
         UpdatePlaybackPosition(clockTime);
+
         nsCOMPtr<nsIRunnable> event =
           NS_NewRunnableMethod(mDecoder, &MediaDecoder::PlaybackEnded);
         NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
       }
       return NS_OK;
     }
   }
 
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.h
@@ -47,16 +47,19 @@ public:
       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()) {
+        // XXX we really should check whether 'stream' has finished within time aTo,
+        // not just that it's finishing when all its queued data eventually runs
+        // out.
         allFinished = false;
       }
       if (!stream->HasCurrentData()) {
         allHaveCurrentData = false;
       }
       for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer());
            !tracks.IsEnded(); tracks.Next()) {
         bool found = false;