Bug 907162 - Fix MediaDecoderStateMachine might dispatch MediaDecoder::PlaybackEnded more than once and trigger multiple 'ended' events in HTMLMediaElement. r=cpearce
authorJW Wang <jwwang@mozilla.com>
Mon, 17 Mar 2014 10:12:20 +0800
changeset 186157 c298b8aa94c9158f8f8667a02d7d2eb81f67aa27
parent 186156 28ab518279e35981572fee14f506641f4843555c
child 186158 8f6b2d2b2000cd84d1eaba766e77c7b1e0cb6d6f
push idunknown
push userunknown
push dateunknown
reviewerscpearce
bugs907162
milestone31.0a1
Bug 907162 - Fix MediaDecoderStateMachine might dispatch MediaDecoder::PlaybackEnded more than once and trigger multiple 'ended' events in HTMLMediaElement. r=cpearce
content/media/MediaDecoderStateMachine.cpp
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -1516,17 +1516,17 @@ MediaDecoderStateMachine::SetReaderActiv
   MOZ_ASSERT(OnDecodeThread());
   mReader->SetActive();
 }
 
 void
 MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
 {
   AssertCurrentThreadInMonitor();
-  
+
   // NeedToDecodeAudio() can go from false to true while we hold the
   // monitor, but it can't go from true to false. This can happen because
   // NeedToDecodeAudio() takes into account the amount of decoded audio
   // that's been written to the AudioStream but not played yet. So if we
   // were calling NeedToDecodeAudio() twice and we thread-context switch
   // between the calls, audio can play, which can affect the return value
   // of NeedToDecodeAudio() giving inconsistent results. So we cache the
   // value returned by NeedToDecodeAudio(), and make decisions
@@ -2269,19 +2269,26 @@ nsresult MediaDecoderStateMachine::RunSt
       // 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);
+        {
+          // Wait for the state change is completed in the main thread,
+          // otherwise we might see |mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING|
+          // in next loop and send |MediaDecoder::PlaybackEnded| again to trigger 'ended'
+          // event twice in the media element.
+          ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+          nsCOMPtr<nsIRunnable> event =
+            NS_NewRunnableMethod(mDecoder, &MediaDecoder::PlaybackEnded);
+          NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
+        }
       }
       return NS_OK;
     }
   }
 
   return NS_OK;
 }
 
@@ -2751,16 +2758,22 @@ nsresult MediaDecoderStateMachine::Sched
       return GetStateMachineThread()->Dispatch(this, NS_DISPATCH_NORMAL);
     }
     // We're not currently running this state machine on the state machine
     // thread, but something has already dispatched an event to run it again,
     // so just exit; it's going to run real soon.
     return NS_OK;
   }
 
+  // Since there is already a pending task that will run immediately,
+  // we don't need to schedule a timer task.
+  if (mRunAgain) {
+    return NS_OK;
+  }
+
   mTimeout = timeout;
 
   nsresult res;
   if (!mTimer) {
     mTimer = do_CreateInstance("@mozilla.org/timer;1", &res);
     if (NS_FAILED(res)) return res;
     mTimer->SetTarget(GetStateMachineThread());
   }