Bug 1128357 Patch 2: Don't dispatch seeking/seeked events when coming out of dormant mode r=cpearce
authorSotaro Ikeda <sikeda@mozilla.com>
Wed, 04 Mar 2015 17:33:40 -0800
changeset 231944 18225b2d31bad66023058e2b80807ab6c756c6c6
parent 231943 3db16602084f73853db796a31729fb23ee96f008
child 231945 9b100a29161418cd7f69d0b5d14f29df1f911b60
push id56402
push usersikeda@mozilla.com
push dateThu, 05 Mar 2015 01:35:16 +0000
treeherdermozilla-inbound@d78b8a0b6392 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1128357
milestone39.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 1128357 Patch 2: Don't dispatch seeking/seeked events when coming out of dormant mode r=cpearce
dom/html/HTMLMediaElement.cpp
dom/media/AbstractMediaDecoder.h
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/mediasource/SourceBufferDecoder.cpp
dom/media/mediasource/SourceBufferDecoder.h
dom/media/omx/AudioOffloadPlayer.cpp
dom/media/omx/MediaOmxCommonDecoder.cpp
dom/media/omx/MediaOmxCommonDecoder.h
dom/media/webaudio/BufferDecoder.cpp
dom/media/webaudio/BufferDecoder.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -568,17 +568,17 @@ NS_IMETHODIMP HTMLMediaElement::GetError
 bool
 HTMLMediaElement::Ended()
 {
   if (mSrcStream) {
     return GetSrcMediaStream()->IsFinished();
   }
 
   if (mDecoder) {
-    return mDecoder->IsEnded();
+    return mDecoder->IsEndedOrShutdown();
   }
 
   return false;
 }
 
 NS_IMETHODIMP HTMLMediaElement::GetEnded(bool* aEnded)
 {
   *aEnded = Ended();
@@ -2194,17 +2194,17 @@ HTMLMediaElement::Play(ErrorResult& aRv)
     }
   }
   if (mSuspendedForPreloadNone) {
     ResumeLoad(PRELOAD_ENOUGH);
   }
   // Even if we just did Load() or ResumeLoad(), we could already have a decoder
   // here if we managed to clone an existing decoder.
   if (mDecoder) {
-    if (mDecoder->IsEnded()) {
+    if (mDecoder->IsEndedOrShutdown()) {
       SetCurrentTime(0);
     }
     if (!mPausedForInactiveDocumentOrChannel) {
       aRv = mDecoder->Play();
       if (aRv.Failed()) {
         return;
       }
     }
@@ -3172,17 +3172,17 @@ void HTMLMediaElement::Error(uint16_t aE
   ChangeDelayLoadStatus(false);
 }
 
 void HTMLMediaElement::PlaybackEnded()
 {
   // We changed state which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
 
-  NS_ASSERTION(!mDecoder || mDecoder->IsEnded(),
+  NS_ASSERTION(!mDecoder || mDecoder->IsEndedOrShutdown(),
                "Decoder fired ended, but not in ended state");
 
   // Discard all output streams that have finished now.
   for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
     if (mOutputStreams[i].mFinishWhenEnded) {
       mOutputStreams.RemoveElementAt(i);
     }
   }
@@ -3409,17 +3409,17 @@ void HTMLMediaElement::UpdateReadyStateF
     // Don't advance if we are playing video, but don't have a video frame.
     // Also, if video became available after advancing to HAVE_CURRENT_DATA
     // while we are still playing, we need to revert to HAVE_METADATA until
     // a video frame is available.
     ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
     return;
   }
 
-  if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEnded()) {
+  if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEndedOrShutdown()) {
     // The decoder has signaled that the download has been suspended by the
     // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's
     // script waiting for a "canplaythrough" event; without this forced
     // transition, we will never fire the "canplaythrough" event if the
     // media cache is too small, and scripts are bound to fail. Don't force
     // this transition if the decoder is in ended state; the readyState
     // should remain at HAVE_CURRENT_DATA in this case.
     // Note that this state transition includes the case where we finished
@@ -3692,17 +3692,17 @@ bool HTMLMediaElement::IsPotentiallyPlay
 }
 
 bool HTMLMediaElement::IsPlaybackEnded() const
 {
   // TODO:
   //   the current playback position is equal to the effective end of the media resource.
   //   See bug 449157.
   return mReadyState >= nsIDOMHTMLMediaElement::HAVE_METADATA &&
-    mDecoder ? mDecoder->IsEnded() : false;
+    mDecoder ? mDecoder->IsEndedOrShutdown() : false;
 }
 
 already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentPrincipal()
 {
   if (mDecoder) {
     return mDecoder->GetCurrentPrincipal();
   }
   if (mSrcStream) {
@@ -3766,17 +3766,17 @@ void HTMLMediaElement::SuspendOrResumeEl
       }
       mEventDeliveryPaused = aSuspendEvents;
     } else {
 #ifdef MOZ_EME
       MOZ_ASSERT(!mMediaKeys);
 #endif
       if (mDecoder) {
         mDecoder->Resume(false);
-        if (!mPaused && !mDecoder->IsEnded()) {
+        if (!mPaused && !mDecoder->IsEndedOrShutdown()) {
           mDecoder->Play();
         }
       } else if (mSrcStream) {
         GetSrcMediaStream()->ChangeExplicitBlockerCount(-1);
       }
       if (mEventDeliveryPaused) {
         mEventDeliveryPaused = false;
         DispatchPendingMediaEvents();
@@ -3827,17 +3827,17 @@ void HTMLMediaElement::AddRemoveSelfRefe
   // that's covered by the !mPaused check.
   nsIDocument* ownerDoc = OwnerDoc();
 
   // See the comment at the top of this file for the explanation of this
   // boolean expression.
   bool needSelfReference = !mShuttingDown &&
     ownerDoc->IsActive() &&
     (mDelayingLoadEvent ||
-     (!mPaused && mDecoder && !mDecoder->IsEnded()) ||
+     (!mPaused && mDecoder && !mDecoder->IsEndedOrShutdown()) ||
      (!mPaused && mSrcStream && !mSrcStream->IsFinished()) ||
      (mDecoder && mDecoder->IsSeeking()) ||
      CanActivateAutoplay() ||
      (mMediaSource ? mProgressTimer :
       mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING));
 
   if (needSelfReference != mHasSelfReference) {
     mHasSelfReference = needSelfReference;
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -30,16 +30,21 @@ class CDMProxy;
 #endif
 
 typedef nsDataHashtable<nsCStringHashKey, nsCString> MetadataTags;
 
 static inline bool IsCurrentThread(nsIThread* aThread) {
   return NS_GetCurrentThread() == aThread;
 }
 
+enum class MediaDecoderEventVisibility : int8_t {
+  Observable,
+  Suppressed
+};
+
 /**
  * The AbstractMediaDecoder class describes the public interface for a media decoder
  * and is used by the MediaReader classes.
  */
 class AbstractMediaDecoder : public nsISupports
 {
 public:
   // Returns the monitor for other threads to synchronise access to
@@ -85,19 +90,19 @@ public:
   virtual mozilla::layers::ImageContainer* GetImageContainer() = 0;
 
   // Return true if the media layer supports seeking.
   virtual bool IsTransportSeekable() = 0;
 
   // Return true if the transport layer supports seeking.
   virtual bool IsMediaSeekable() = 0;
 
-  virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags, bool aRestoredFromDormant) = 0;
+  virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags, MediaDecoderEventVisibility aEventVisibility) = 0;
   virtual void QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) = 0;
-  virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo, bool aRestoredFromDormant) = 0;
+  virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo, MediaDecoderEventVisibility aEventVisibility) = 0;
 
   virtual void RemoveMediaTracks() = 0;
 
   // Set the media end time in microseconds
   virtual void SetMediaEndTime(int64_t aTime) = 0;
 
   // Make the decoder state machine update the playback position. Called by
   // the reader on the decoder thread (Assertions for this checked by
@@ -161,78 +166,78 @@ public:
 };
 
 class MetadataContainer
 {
 protected:
   MetadataContainer(AbstractMediaDecoder* aDecoder,
                     nsAutoPtr<MediaInfo> aInfo,
                     nsAutoPtr<MetadataTags> aTags,
-                    bool aRestoredFromDormant)
+                    MediaDecoderEventVisibility aEventVisibility)
     : mDecoder(aDecoder),
       mInfo(aInfo),
       mTags(aTags),
-      mRestoredFromDormant(aRestoredFromDormant)
+      mEventVisibility(aEventVisibility)
   {}
 
   nsRefPtr<AbstractMediaDecoder> mDecoder;
   nsAutoPtr<MediaInfo>  mInfo;
   nsAutoPtr<MetadataTags> mTags;
-  bool mRestoredFromDormant;
+  MediaDecoderEventVisibility mEventVisibility;
 };
 
 class MetadataEventRunner : public nsRunnable, private MetadataContainer
 {
 public:
   MetadataEventRunner(AbstractMediaDecoder* aDecoder,
                       nsAutoPtr<MediaInfo> aInfo,
                       nsAutoPtr<MetadataTags> aTags,
-                      bool aRestoredFromDormant = false)
-    : MetadataContainer(aDecoder, aInfo, aTags, aRestoredFromDormant)
+                      MediaDecoderEventVisibility aEventVisibility = MediaDecoderEventVisibility::Observable)
+    : MetadataContainer(aDecoder, aInfo, aTags, aEventVisibility)
   {}
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
-    mDecoder->MetadataLoaded(mInfo, mTags, mRestoredFromDormant);
+    mDecoder->MetadataLoaded(mInfo, mTags, mEventVisibility);
     return NS_OK;
   }
 };
 
 class FirstFrameLoadedEventRunner : public nsRunnable, private MetadataContainer
 {
 public:
   FirstFrameLoadedEventRunner(AbstractMediaDecoder* aDecoder,
                               nsAutoPtr<MediaInfo> aInfo,
-                              bool aRestoredFromDormant = false)
-    : MetadataContainer(aDecoder, aInfo, nsAutoPtr<MetadataTags>(nullptr), aRestoredFromDormant)
+                              MediaDecoderEventVisibility aEventVisibility = MediaDecoderEventVisibility::Observable)
+    : MetadataContainer(aDecoder, aInfo, nsAutoPtr<MetadataTags>(nullptr), aEventVisibility)
   {}
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
-    mDecoder->FirstFrameLoaded(mInfo, mRestoredFromDormant);
+    mDecoder->FirstFrameLoaded(mInfo, mEventVisibility);
     return NS_OK;
   }
 };
 
 class MetadataUpdatedEventRunner : public nsRunnable, private MetadataContainer
 {
 public:
   MetadataUpdatedEventRunner(AbstractMediaDecoder* aDecoder,
                              nsAutoPtr<MediaInfo> aInfo,
                              nsAutoPtr<MetadataTags> aTags,
-                             bool aRestoredFromDormant = false)
-    : MetadataContainer(aDecoder, aInfo, aTags, aRestoredFromDormant)
+                             MediaDecoderEventVisibility aEventVisibility = MediaDecoderEventVisibility::Observable)
+    : MetadataContainer(aDecoder, aInfo, aTags, aEventVisibility)
   {}
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     nsAutoPtr<MediaInfo> info(new MediaInfo());
     *info = *mInfo;
-    mDecoder->MetadataLoaded(info, mTags, mRestoredFromDormant);
-    mDecoder->FirstFrameLoaded(mInfo, mRestoredFromDormant);
+    mDecoder->MetadataLoaded(info, mTags, mEventVisibility);
+    mDecoder->FirstFrameLoaded(mInfo, mEventVisibility);
     return NS_OK;
   }
 };
 
 class RemoveMediaTracksEventRunner : public nsRunnable
 {
 public:
   explicit RemoveMediaTracksEventRunner(AbstractMediaDecoder* aDecoder)
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -165,17 +165,17 @@ void MediaDecoder::UpdateDormantState(bo
     mIsDormant = true;
   }
 #endif
   // Try to enable dormant by idle heuristic, when the owner is hidden.
   bool prevHeuristicDormant = mIsHeuristicDormant;
   mIsHeuristicDormant = false;
   if (mIsHeuristicDormantSupported && mOwner->IsHidden()) {
     if (aDormantTimeout && !aActivity &&
-        (mPlayState == PLAY_STATE_PAUSED || mPlayState == PLAY_STATE_ENDED)) {
+        (mPlayState == PLAY_STATE_PAUSED || IsEnded())) {
       // Enable heuristic dormant
       mIsHeuristicDormant = true;
     } else if(prevHeuristicDormant && !aActivity) {
       // Continue heuristic dormant
       mIsHeuristicDormant = true;
     }
 
     if (mIsHeuristicDormant) {
@@ -186,21 +186,19 @@ void MediaDecoder::UpdateDormantState(bo
   if (prevDormant == mIsDormant) {
     // No update to dormant state
     return;
   }
 
   if (mIsDormant) {
     DECODER_LOG("UpdateDormantState() entering DORMANT state");
     mDecoderStateMachine->SetDormant(true);
-
-    int64_t timeUsecs = 0;
-    SecondsToUsecs(mCurrentTime, timeUsecs);
-    mRequestedSeekTarget = SeekTarget(timeUsecs, SeekTarget::Accurate);
-
+    if (IsEnded()) {
+      mWasEndedWhenEnteredDormant = true;
+    }
     mNextState = mPlayState;
     ChangeState(PLAY_STATE_LOADING);
   } else {
     DECODER_LOG("UpdateDormantState() leaving DORMANT state");
     mDecoderStateMachine->SetDormant(false);
   }
 }
 
@@ -219,17 +217,17 @@ void MediaDecoder::StartDormantTimer()
     return;
   }
 
   if (mIsHeuristicDormant ||
       mShuttingDown ||
       !mOwner ||
       !mOwner->IsHidden() ||
       (mPlayState != PLAY_STATE_PAUSED &&
-       mPlayState != PLAY_STATE_ENDED))
+       !IsEnded()))
   {
     return;
   }
 
   if (!mDormantTimer) {
     mDormantTimer = do_CreateInstance("@mozilla.org/timer;1");
   }
   mDormantTimer->InitWithFuncCallback(&MediaDecoder::DormantTimerExpired,
@@ -246,17 +244,17 @@ void MediaDecoder::CancelDormantTimer()
 }
 
 void MediaDecoder::Pause()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   if (mPlayState == PLAY_STATE_LOADING ||
       mPlayState == PLAY_STATE_SEEKING ||
-      mPlayState == PLAY_STATE_ENDED) {
+      IsEnded()) {
     mNextState = PLAY_STATE_PAUSED;
     return;
   }
 
   ChangeState(PLAY_STATE_PAUSED);
 }
 
 void MediaDecoder::SetVolume(double aVolume)
@@ -579,16 +577,17 @@ MediaDecoder::MediaDecoder() :
   mOwner(nullptr),
   mPlaybackStatistics(new MediaChannelStatistics()),
   mPinnedForSeek(false),
   mShuttingDown(false),
   mPausedForPlaybackRateNull(false),
   mMinimizePreroll(false),
   mMediaTracksConstructed(false),
   mIsDormant(false),
+  mWasEndedWhenEnteredDormant(false),
   mIsHeuristicDormantSupported(
     Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false)),
   mHeuristicDormantTimeout(
     Preferences::GetInt("media.decoder.heuristic.dormant.timeout",
                         DEFAULT_HEURISTIC_DORMANT_TIMEOUT_MSECS)),
   mIsHeuristicDormant(false)
 {
   MOZ_COUNT_CTOR(MediaDecoder);
@@ -747,23 +746,22 @@ nsresult MediaDecoder::Play()
   UpdateDormantState(false /* aDormantTimeout */, true /* aActivity */);
 
   NS_ASSERTION(mDecoderStateMachine != nullptr, "Should have state machine.");
   if (mPausedForPlaybackRateNull) {
     return NS_OK;
   }
   nsresult res = ScheduleStateMachineThread();
   NS_ENSURE_SUCCESS(res,res);
-  if (mPlayState == PLAY_STATE_LOADING || mPlayState == PLAY_STATE_SEEKING) {
+  if (IsEnded()) {
+    return Seek(0, SeekTarget::PrevSyncPoint);
+  } else if (mPlayState == PLAY_STATE_LOADING || mPlayState == PLAY_STATE_SEEKING) {
     mNextState = PLAY_STATE_PLAYING;
     return NS_OK;
   }
-  if (mPlayState == PLAY_STATE_ENDED) {
-    return Seek(0, SeekTarget::PrevSyncPoint);
-  }
 
   ChangeState(PLAY_STATE_PLAYING);
   return NS_OK;
 }
 
 nsresult MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -773,16 +771,17 @@ nsresult MediaDecoder::Seek(double aTime
   MOZ_ASSERT(aTime >= 0.0, "Cannot seek to a negative value.");
 
   int64_t timeUsecs = 0;
   nsresult rv = SecondsToUsecs(aTime, timeUsecs);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mRequestedSeekTarget = SeekTarget(timeUsecs, aSeekType);
   mCurrentTime = aTime;
+  mWasEndedWhenEnteredDormant = false;
 
   // If we are already in the seeking state, the new seek overrides the old one.
   if (mPlayState != PLAY_STATE_LOADING) {
     bool paused = false;
     if (mOwner) {
       paused = mOwner->GetPaused();
     }
     mNextState = paused ? PLAY_STATE_PAUSED : PLAY_STATE_PLAYING;
@@ -837,17 +836,17 @@ MediaDecoder::IsExpectingMoreData()
   }
 
   // Otherwise, we should be getting data unless the stream is suspended.
   return !mResource->IsSuspended();
 }
 
 void MediaDecoder::MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
                                   nsAutoPtr<MetadataTags> aTags,
-                                  bool aRestoredFromDormant)
+                                  MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown) {
     return;
   }
 
   DECODER_LOG("MetadataLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d",
@@ -867,17 +866,17 @@ void MediaDecoder::MetadataLoaded(nsAuto
 
   mInfo = aInfo.forget();
   ConstructMediaTracks();
 
   if (mOwner) {
     // Make sure the element and the frame (if any) are told about
     // our new size.
     Invalidate();
-    if (!aRestoredFromDormant) {
+    if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
       mOwner->MetadataLoaded(mInfo, nsAutoPtr<const MetadataTags>(aTags.forget()));
     }
   }
 }
 
 const char*
 MediaDecoder::PlayStateStr()
 {
@@ -889,33 +888,33 @@ MediaDecoder::PlayStateStr()
     case PLAY_STATE_SEEKING: return "PLAY_STATE_SEEKING";
     case PLAY_STATE_ENDED: return "PLAY_STATE_ENDED";
     case PLAY_STATE_SHUTDOWN: return "PLAY_STATE_SHUTDOWN";
     default: return "INVALID_PLAY_STATE";
   }
 }
 
 void MediaDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
-                                    bool aRestoredFromDormant)
+                                    MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown) {
     return;
   }
 
   DECODER_LOG("FirstFrameLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d mPlayState=%s mIsDormant=%d",
               aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
               aInfo->HasAudio(), aInfo->HasVideo(), PlayStateStr(), mIsDormant);
 
   mInfo = aInfo.forget();
 
   if (mOwner) {
     Invalidate();
-    if (!aRestoredFromDormant) {
+    if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
       mOwner->FirstFrameLoaded();
     }
   }
 
   // This can run cache callbacks.
   mResource->EnsureCacheUpToDate();
 
   // The element can run javascript via events
@@ -991,20 +990,26 @@ bool MediaDecoder::IsSameOriginMedia()
 
 bool MediaDecoder::IsSeeking() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   return !mIsDormant && (mPlayState == PLAY_STATE_SEEKING ||
     (mPlayState == PLAY_STATE_LOADING && mRequestedSeekTarget.IsValid()));
 }
 
+bool MediaDecoder::IsEndedOrShutdown() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return IsEnded() || mPlayState == PLAY_STATE_SHUTDOWN;
+}
+
 bool MediaDecoder::IsEnded() const
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  return mPlayState == PLAY_STATE_ENDED || mPlayState == PLAY_STATE_SHUTDOWN;
+  return mPlayState == PLAY_STATE_ENDED ||
+         (mWasEndedWhenEnteredDormant && (mPlayState != PLAY_STATE_SHUTDOWN));
 }
 
 void MediaDecoder::PlaybackEnded()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown ||
       mPlayState == PLAY_STATE_SEEKING ||
@@ -1177,24 +1182,25 @@ void MediaDecoder::NotifyBytesConsumed(i
     mPlaybackStatistics->AddBytes(aBytes);
   }
   mDecoderPosition = aOffset + aBytes;
 }
 
 void MediaDecoder::UpdateReadyStateForData()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (!mOwner || mShuttingDown || !mDecoderStateMachine)
+  if (!mOwner || mShuttingDown || !mDecoderStateMachine) {
     return;
+  }
   MediaDecoderOwner::NextFrameStatus frameStatus =
     mDecoderStateMachine->GetNextFrameStatus();
   mOwner->UpdateReadyStateForData(frameStatus);
 }
 
-void MediaDecoder::SeekingStopped()
+void MediaDecoder::SeekingStopped(MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown)
     return;
 
   bool seekWasAborted = false;
   {
@@ -1202,33 +1208,35 @@ void MediaDecoder::SeekingStopped()
 
     // An additional seek was requested while the current seek was
     // in operation.
     if (mRequestedSeekTarget.IsValid()) {
       ChangeState(PLAY_STATE_SEEKING);
       seekWasAborted = true;
     } else {
       UnpinForSeek();
-      ChangeState(mNextState);
+      if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
+        ChangeState(mNextState);
+      }
     }
   }
 
-  PlaybackPositionChanged();
+  PlaybackPositionChanged(aEventVisibility);
 
   if (mOwner) {
     UpdateReadyStateForData();
-    if (!seekWasAborted) {
+    if (!seekWasAborted && (aEventVisibility != MediaDecoderEventVisibility::Suppressed)) {
       mOwner->SeekCompleted();
     }
   }
 }
 
 // This is called when seeking stopped *and* we're at the end of the
 // media.
-void MediaDecoder::SeekingStoppedAtEnd()
+void MediaDecoder::SeekingStoppedAtEnd(MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown)
     return;
 
   bool fireEnded = false;
   bool seekWasAborted = false;
@@ -1242,38 +1250,40 @@ void MediaDecoder::SeekingStoppedAtEnd()
       seekWasAborted = true;
     } else {
       UnpinForSeek();
       fireEnded = true;
       ChangeState(PLAY_STATE_ENDED);
     }
   }
 
-  PlaybackPositionChanged();
+  PlaybackPositionChanged(aEventVisibility);
 
   if (mOwner) {
     UpdateReadyStateForData();
-    if (!seekWasAborted) {
+    if (!seekWasAborted && (aEventVisibility != MediaDecoderEventVisibility::Suppressed)) {
       mOwner->SeekCompleted();
       if (fireEnded) {
         mOwner->PlaybackEnded();
       }
     }
   }
 }
 
-void MediaDecoder::SeekingStarted()
+void MediaDecoder::SeekingStarted(MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown)
     return;
 
   if (mOwner) {
     UpdateReadyStateForData();
-    mOwner->SeekStarted();
+    if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
+      mOwner->SeekStarted();
+    }
   }
 }
 
 void MediaDecoder::ChangeState(PlayState aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
 
@@ -1295,17 +1305,17 @@ void MediaDecoder::ChangeState(PlayState
   }
 
   DECODER_LOG("ChangeState %s => %s",
               gPlayStateStr[mPlayState], gPlayStateStr[aState]);
   mPlayState = aState;
 
   if (mPlayState == PLAY_STATE_PLAYING) {
     ConstructMediaTracks();
-  } else if (mPlayState == PLAY_STATE_ENDED) {
+  } else if (IsEnded()) {
     RemoveMediaTracks();
   }
 
   ApplyStateToStateMachine(mPlayState);
 
   CancelDormantTimer();
   // Start dormant timer if necessary
   StartDormantTimer();
@@ -1331,17 +1341,17 @@ void MediaDecoder::ApplyStateToStateMach
         // The state machine checks for things like PAUSED in RunStateMachine.
         // Make sure to keep it in the loop.
         ScheduleStateMachineThread();
         break;
     }
   }
 }
 
-void MediaDecoder::PlaybackPositionChanged()
+void MediaDecoder::PlaybackPositionChanged(MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown)
     return;
 
   double lastTime = mCurrentTime;
 
   // Control the scope of the monitor so it is not
@@ -1367,17 +1377,19 @@ void MediaDecoder::PlaybackPositionChang
   }
 
   // Invalidate the frame so any video data is displayed.
   // Do this before the timeupdate event so that if that
   // event runs JavaScript that queries the media size, the
   // frame has reflowed and the size updated beforehand.
   Invalidate();
 
-  if (mOwner && lastTime != mCurrentTime) {
+  if (mOwner &&
+      (aEventVisibility != MediaDecoderEventVisibility::Suppressed) &&
+      lastTime != mCurrentTime) {
     FireTimeUpdate();
   }
 }
 
 void MediaDecoder::DurationChanged()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -230,41 +230,48 @@ struct SeekTarget {
   enum Type {
     Invalid,
     PrevSyncPoint,
     Accurate
   };
   SeekTarget()
     : mTime(-1.0)
     , mType(SeekTarget::Invalid)
+    , mEventVisibility(MediaDecoderEventVisibility::Observable)
   {
   }
-  SeekTarget(int64_t aTimeUsecs, Type aType)
+  SeekTarget(int64_t aTimeUsecs,
+             Type aType,
+             MediaDecoderEventVisibility aEventVisibility =
+               MediaDecoderEventVisibility::Observable)
     : mTime(aTimeUsecs)
     , mType(aType)
+    , mEventVisibility(aEventVisibility)
   {
   }
   SeekTarget(const SeekTarget& aOther)
     : mTime(aOther.mTime)
     , mType(aOther.mType)
+    , mEventVisibility(aOther.mEventVisibility)
   {
   }
   bool IsValid() const {
     return mType != SeekTarget::Invalid;
   }
   void Reset() {
     mTime = -1;
     mType = SeekTarget::Invalid;
   }
   // Seek target time in microseconds.
   int64_t mTime;
   // Whether we should seek "Fast", or "Accurate".
   // "Fast" seeks to the seek point preceeding mTime, whereas
   // "Accurate" seeks as close as possible to mTime.
   Type mType;
+  MediaDecoderEventVisibility mEventVisibility;
 };
 
 class MediaDecoder : public nsIObserver,
                      public AbstractMediaDecoder
 {
 public:
   class DecodedStreamGraphListener;
 
@@ -571,19 +578,20 @@ public:
   // from the resource. Called on the main by an event runner dispatched
   // by the MediaResource read functions.
   void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE;
 
   // Return true if we are currently seeking in the media resource.
   // Call on the main thread only.
   virtual bool IsSeeking() const;
 
-  // Return true if the decoder has reached the end of playback.
+  // Return true if the decoder has reached the end of playback or the decoder
+  // has shutdown.
   // Call on the main thread only.
-  virtual bool IsEnded() const;
+  virtual bool IsEndedOrShutdown() const;
 
   // Set the duration of the media resource in units of seconds.
   // This is called via a channel listener if it can pick up the duration
   // from a content header. Must be called from the main thread only.
   virtual void SetDuration(double aDuration);
 
   // Sets the initial duration of the media. Called while the media metadata
   // is being read and the decode is being setup.
@@ -761,22 +769,22 @@ public:
   // May be called by the reader to notify this decoder that the metadata from
   // the media file has been read. Call on the decode thread only.
   void OnReadMetadataCompleted() MOZ_OVERRIDE { }
 
   // Called when the metadata from the media file has been loaded by the
   // state machine. Call on the main thread only.
   virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
                               nsAutoPtr<MetadataTags> aTags,
-                              bool aRestoredFromDormant) MOZ_OVERRIDE;
+                              MediaDecoderEventVisibility aEventVisibility) MOZ_OVERRIDE;
 
   // Called when the first audio and/or video from the media file has been loaded
   // by the state machine. Call on the main thread only.
   virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
-                                bool aRestoredFromDormant) MOZ_OVERRIDE;
+                                MediaDecoderEventVisibility aEventVisibility) MOZ_OVERRIDE;
 
   // Called from MetadataLoaded(). Creates audio tracks and adds them to its
   // owner's audio track list, and implies to video tracks respectively.
   // Call on the main thread only.
   void ConstructMediaTracks();
 
   // Removes all audio tracks and video tracks that are previously added into
   // the track list. Call on the main thread only.
@@ -790,30 +798,30 @@ public:
   virtual bool IsExpectingMoreData();
 
   // Called when the video has completed playing.
   // Call on the main thread only.
   void PlaybackEnded();
 
   // Seeking has stopped. Inform the element on the main
   // thread.
-  void SeekingStopped();
+  void SeekingStopped(MediaDecoderEventVisibility aEventVisibility = MediaDecoderEventVisibility::Observable);
 
   // Seeking has stopped at the end of the resource. Inform the element on the main
   // thread.
-  void SeekingStoppedAtEnd();
+  void SeekingStoppedAtEnd(MediaDecoderEventVisibility aEventVisibility = MediaDecoderEventVisibility::Observable);
 
   // Seeking has started. Inform the element on the main
   // thread.
-  void SeekingStarted();
+  void SeekingStarted(MediaDecoderEventVisibility aEventVisibility = MediaDecoderEventVisibility::Observable);
 
   // Called when the backend has changed the current playback
   // position. It dispatches a timeupdate event and invalidates the frame.
   // This must be called on the main thread only.
-  virtual void PlaybackPositionChanged();
+  virtual void PlaybackPositionChanged(MediaDecoderEventVisibility aEventVisibility = MediaDecoderEventVisibility::Observable);
 
   // Calls mElement->UpdateReadyStateForData, telling it whether we have
   // data for the next frame and if we're buffering. Main thread only.
   virtual void UpdateReadyStateForData();
 
   // Find the end of the cached data starting at the current decoder
   // position.
   int64_t GetDownloadPosition();
@@ -1023,16 +1031,19 @@ protected:
   static void DormantTimerExpired(nsITimer *aTimer, void *aClosure);
 
   // Start a timer for heuristic dormant.
   void StartDormantTimer();
 
   // Cancel a timer for heuristic dormant.
   void CancelDormantTimer();
 
+  // Return true if the decoder has reached the end of playback
+  bool IsEnded() const;
+
   /******
    * The following members should be accessed with the decoder lock held.
    ******/
 
   // Current decoding position in the stream. This is where the decoder
   // is up to consuming the stream. This is not adjusted during decoder
   // seek operations, but it's updated at the end when we start playing
   // back again.
@@ -1191,16 +1202,22 @@ protected:
 
   // Stores media info, including info of audio tracks and video tracks, should
   // only be accessed from main thread.
   nsAutoPtr<MediaInfo> mInfo;
 
   // True if MediaDecoder is in dormant state.
   bool mIsDormant;
 
+  // True if MediaDecoder was PLAY_STATE_ENDED state, when entering to dormant.
+  // When MediaCodec is in dormant during PLAY_STATE_ENDED state, PlayState
+  // becomes different from PLAY_STATE_ENDED. But the MediaDecoder need to act
+  // as in PLAY_STATE_ENDED state to MediaDecoderOwner.
+  bool mWasEndedWhenEnteredDormant;
+
   // True if heuristic dormant is supported.
   const bool mIsHeuristicDormantSupported;
 
   // Timeout ms of heuristic dormant timer.
   const int mHeuristicDormantTimeout;
 
   // True if MediaDecoder is in dormant by heuristic.
   bool mIsHeuristicDormant;
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1240,17 +1240,20 @@ void MediaDecoderStateMachine::UpdatePla
 void MediaDecoderStateMachine::UpdatePlaybackPosition(int64_t aTime)
 {
   UpdatePlaybackPositionInternal(aTime);
 
   bool fragmentEnded = mFragmentEndTime >= 0 && GetMediaTime() >= mFragmentEndTime;
   if (!mPositionChangeQueued || fragmentEnded) {
     mPositionChangeQueued = true;
     nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(mDecoder, &MediaDecoder::PlaybackPositionChanged);
+      NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
+        mDecoder,
+        &MediaDecoder::PlaybackPositionChanged,
+        MediaDecoderEventVisibility::Observable);
     NS_DispatchToMainThread(event);
   }
 
   mMetadataManager.DispatchMetadataIfNeeded(mDecoder, aTime);
 
   if (fragmentEnded) {
     StopPlayback();
   }
@@ -1452,29 +1455,43 @@ bool MediaDecoderStateMachine::IsDormant
   return mReader->IsDormantNeeded();
 }
 
 void MediaDecoderStateMachine::SetDormant(bool aDormant)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   AssertCurrentThreadInMonitor();
 
+  if (mState == DECODER_STATE_SHUTDOWN) {
+    return;
+  }
+
   if (!mReader) {
     return;
   }
 
   DECODER_LOG("SetDormant=%d", aDormant);
 
   if (aDormant) {
-    if (mState == DECODER_STATE_SEEKING && !mQueuedSeekTarget.IsValid()) {
-      if (mSeekTarget.IsValid()) {
+    if (mState == DECODER_STATE_SEEKING) {
+      if (mQueuedSeekTarget.IsValid()) {
+        // Keep latest seek target
+      } else if (mSeekTarget.IsValid()) {
         mQueuedSeekTarget = mSeekTarget;
       } else if (mCurrentSeekTarget.IsValid()) {
         mQueuedSeekTarget = mCurrentSeekTarget;
+      } else {
+        mQueuedSeekTarget = SeekTarget(mCurrentFrameTime,
+                                       SeekTarget::Accurate,
+                                       MediaDecoderEventVisibility::Suppressed);
       }
+    } else {
+      mQueuedSeekTarget = SeekTarget(mCurrentFrameTime,
+                                     SeekTarget::Accurate,
+                                     MediaDecoderEventVisibility::Suppressed);
     }
     mSeekTarget.Reset();
     mCurrentSeekTarget.Reset();
     ScheduleStateMachine();
     SetState(DECODER_STATE_DORMANT);
     mDecoder->GetReentrantMonitor().NotifyAll();
   } else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) {
     mDecodingFrozenAtStateDecoding = true;
@@ -1735,17 +1752,17 @@ MediaDecoderStateMachine::StartSeek(cons
   int64_t end = GetEndTime();
   NS_ASSERTION(mStartTime != -1, "Should know start time by now");
   NS_ASSERTION(end != -1, "Should know end time by now");
   int64_t seekTime = aTarget.mTime + mStartTime;
   seekTime = std::min(seekTime, end);
   seekTime = std::max(mStartTime, seekTime);
   NS_ASSERTION(seekTime >= mStartTime && seekTime <= end,
                "Can only seek in range [0,duration]");
-  mSeekTarget = SeekTarget(seekTime, aTarget.mType);
+  mSeekTarget = SeekTarget(seekTime, aTarget.mType, aTarget.mEventVisibility);
 
   DECODER_LOG("Changed state to SEEKING (to %lld)", mSeekTarget.mTime);
   SetState(DECODER_STATE_SEEKING);
 
   // TODO: We should re-create the decoded stream after seek completed as we do
   // for audio thread since it is until then we know which position we seek to
   // as far as fast-seek is concerned. It also fix the problem where stream
   // clock seems to go backwards during seeking.
@@ -2252,29 +2269,35 @@ nsresult MediaDecoderStateMachine::Decod
   return NS_OK;
 }
 
 void
 MediaDecoderStateMachine::EnqueueLoadedMetadataEvent()
 {
   nsAutoPtr<MediaInfo> info(new MediaInfo());
   *info = mInfo;
+  MediaDecoderEventVisibility visibility = mSentLoadedMetadataEvent?
+                                    MediaDecoderEventVisibility::Suppressed :
+                                    MediaDecoderEventVisibility::Observable;
   nsCOMPtr<nsIRunnable> metadataLoadedEvent =
-    new MetadataEventRunner(mDecoder, info, mMetadataTags, mSentLoadedMetadataEvent);
+    new MetadataEventRunner(mDecoder, info, mMetadataTags, visibility);
   NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
   mSentLoadedMetadataEvent = true;
 }
 
 void
 MediaDecoderStateMachine::EnqueueFirstFrameLoadedEvent()
 {
   nsAutoPtr<MediaInfo> info(new MediaInfo());
   *info = mInfo;
+  MediaDecoderEventVisibility visibility = mSentFirstFrameLoadedEvent?
+                                    MediaDecoderEventVisibility::Suppressed :
+                                    MediaDecoderEventVisibility::Observable;
   nsCOMPtr<nsIRunnable> event =
-    new FirstFrameLoadedEventRunner(mDecoder, info, mSentFirstFrameLoadedEvent);
+    new FirstFrameLoadedEventRunner(mDecoder, info, visibility);
   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   mSentFirstFrameLoadedEvent = true;
 }
 
 void
 MediaDecoderStateMachine::CallDecodeFirstFrame()
 {
   MOZ_ASSERT(OnStateMachineThread());
@@ -2469,17 +2492,20 @@ void MediaDecoderStateMachine::DecodeSee
   }
 
   // SeekingStarted will do a UpdateReadyStateForData which will
   // inform the element and its users that we have no frames
   // to display
   {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     nsCOMPtr<nsIRunnable> startEvent =
-      NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStarted);
+      NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
+        mDecoder,
+        &MediaDecoder::SeekingStarted,
+        mCurrentSeekTarget.mEventVisibility);
     NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
   }
   if (mState != DECODER_STATE_SEEKING) {
     // May have shutdown while we released the monitor.
     return;
   }
 
   mDecodeToSeekTarget = false;
@@ -2547,16 +2573,25 @@ MediaDecoderStateMachine::OnSeekFailed(n
   mCancelingSeek = false;
 
   if (NS_FAILED(aResult)) {
     DecodeError();
   } else if (wasCanceled && mSeekTarget.IsValid() && mState == DECODER_STATE_SEEKING) {
     // Try again.
     mCurrentSeekTarget = mSeekTarget;
     mSeekTarget.Reset();
+    {
+      ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+      nsCOMPtr<nsIRunnable> startEvent =
+        NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
+          mDecoder,
+          &MediaDecoder::SeekingStarted,
+          mCurrentSeekTarget.mEventVisibility);
+      NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
+    }
     mReader->Seek(mCurrentSeekTarget.mTime, mEndTime)
            ->Then(DecodeTaskQueue(), __func__, this,
                   &MediaDecoderStateMachine::OnSeekCompleted,
                   &MediaDecoderStateMachine::OnSeekFailed);
     mWaitingForDecoderSeek = true;
   }
 }
 
@@ -2633,24 +2668,30 @@ MediaDecoderStateMachine::SeekCompleted(
     // for the seeking.
     DECODER_LOG("A new seek came along while we were finishing the old one - staying in SEEKING");
     SetState(DECODER_STATE_SEEKING);
   } else if (GetMediaTime() == mEndTime && !isLiveStream) {
     // Seeked to end of media, move to COMPLETED state. Note we don't do
     // this if we're playing a live stream, since the end of media will advance
     // once we download more data!
     DECODER_LOG("Changed state from SEEKING (to %lld) to COMPLETED", seekTime);
-    stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStoppedAtEnd);
+    stopEvent = NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
+                  mDecoder,
+                  &MediaDecoder::SeekingStoppedAtEnd,
+                  mCurrentSeekTarget.mEventVisibility);
     // Explicitly set our state so we don't decode further, and so
     // we report playback ended to the media element.
     SetState(DECODER_STATE_COMPLETED);
     DispatchDecodeTasksIfNeeded();
   } else {
     DECODER_LOG("Changed state from SEEKING (to %lld) to DECODING", seekTime);
-    stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStopped);
+    stopEvent = NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
+                  mDecoder,
+                  &MediaDecoder::SeekingStopped,
+                  mCurrentSeekTarget.mEventVisibility);
     StartDecoding();
   }
 
   // Ensure timestamps are up to date.
   UpdatePlaybackPositionInternal(newCurrentTime);
 
   // Try to decode another frame to detect if we're at the end...
   DECODER_LOG("Seek completed, mCurrentFrameTime=%lld", mCurrentFrameTime);
--- a/dom/media/mediasource/SourceBufferDecoder.cpp
+++ b/dom/media/mediasource/SourceBufferDecoder.cpp
@@ -94,24 +94,24 @@ SourceBufferDecoder::IsMediaSeekable()
 {
   MSE_DEBUG("UNIMPLEMENTED");
   return false;
 }
 
 void
 SourceBufferDecoder::MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
                                     nsAutoPtr<MetadataTags> aTags,
-                                    bool aRestoredFromDormant)
+                                    MediaDecoderEventVisibility aEventVisibility)
 {
   MSE_DEBUG("UNIMPLEMENTED");
 }
 
 void
 SourceBufferDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
-                                      bool aRestoredFromDormant)
+                                      MediaDecoderEventVisibility aEventVisibility)
 {
   MSE_DEBUG("UNIMPLEMENTED");
 }
 
 void
 SourceBufferDecoder::QueueMetadata(int64_t aTime,
                                    nsAutoPtr<MediaInfo> aInfo,
                                    nsAutoPtr<MetadataTags> aTags)
--- a/dom/media/mediasource/SourceBufferDecoder.h
+++ b/dom/media/mediasource/SourceBufferDecoder.h
@@ -43,18 +43,21 @@ public:
   virtual bool OnDecodeThread() const MOZ_FINAL MOZ_OVERRIDE;
   virtual bool OnStateMachineThread() const MOZ_FINAL MOZ_OVERRIDE;
   virtual int64_t GetMediaDuration() MOZ_FINAL MOZ_OVERRIDE;
   virtual layers::ImageContainer* GetImageContainer() MOZ_FINAL MOZ_OVERRIDE;
   virtual MediaDecoderOwner* GetOwner() MOZ_FINAL MOZ_OVERRIDE;
   virtual SourceBufferResource* GetResource() const MOZ_FINAL MOZ_OVERRIDE;
   virtual ReentrantMonitor& GetReentrantMonitor() MOZ_FINAL MOZ_OVERRIDE;
   virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE;
-  virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags, bool aRestoredFromDormant) MOZ_FINAL MOZ_OVERRIDE;
-  virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo, bool aRestoredFromDormant) MOZ_FINAL MOZ_OVERRIDE;
+  virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
+                              nsAutoPtr<MetadataTags> aTags,
+                              MediaDecoderEventVisibility aEventVisibility) MOZ_FINAL MOZ_OVERRIDE;
+  virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
+                                MediaDecoderEventVisibility aEventVisibility) MOZ_FINAL MOZ_OVERRIDE;
   virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE;
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE;
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded, uint32_t aDropped) MOZ_FINAL MOZ_OVERRIDE;
   virtual void NotifyWaitingForResourcesStatusChanged() MOZ_FINAL MOZ_OVERRIDE;
   virtual void OnReadMetadataCompleted() MOZ_FINAL MOZ_OVERRIDE;
   virtual void QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) MOZ_FINAL MOZ_OVERRIDE;
   virtual void RemoveMediaTracks() MOZ_FINAL MOZ_OVERRIDE;
   virtual void SetMediaDuration(int64_t aDuration) MOZ_FINAL MOZ_OVERRIDE;
--- a/dom/media/omx/AudioOffloadPlayer.cpp
+++ b/dom/media/omx/AudioOffloadPlayer.cpp
@@ -355,18 +355,21 @@ status_t AudioOffloadPlayer::SeekTo(int6
   mSeeking = true;
   mReachedEOS = false;
   mPositionTimeMediaUs = -1;
   mSeekTimeUs = aTimeUs;
   mStartPosUs = aTimeUs;
   mDispatchSeekEvents = aDispatchSeekEvents;
 
   if (mDispatchSeekEvents) {
-    nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
-        &MediaDecoder::SeekingStarted);
+    nsCOMPtr<nsIRunnable> nsEvent =
+      NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
+        mObserver,
+        &MediaDecoder::SeekingStarted,
+        MediaDecoderEventVisibility::Observable);
     NS_DispatchToCurrentThread(nsEvent);
   }
 
   if (mPlaying) {
     mAudioSink->Pause();
     mAudioSink->Flush();
     mAudioSink->Start();
 
@@ -375,18 +378,21 @@ status_t AudioOffloadPlayer::SeekTo(int6
 
     if (mStarted) {
       mAudioSink->Flush();
     }
 
     if (mDispatchSeekEvents) {
       mDispatchSeekEvents = false;
       AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Fake seek complete during pause"));
-      nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
-          &MediaDecoder::SeekingStopped);
+      nsCOMPtr<nsIRunnable> nsEvent =
+        NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
+          mObserver,
+          &MediaDecoder::SeekingStopped,
+          MediaDecoderEventVisibility::Observable);
       NS_DispatchToCurrentThread(nsEvent);
     }
   }
 
   return OK;
 }
 
 double AudioOffloadPlayer::GetMediaTimeSecs()
@@ -435,18 +441,21 @@ void AudioOffloadPlayer::NotifyAudioEOS(
 {
   nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
       &MediaDecoder::PlaybackEnded);
   NS_DispatchToMainThread(nsEvent);
 }
 
 void AudioOffloadPlayer::NotifyPositionChanged()
 {
-  nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
-      &MediaOmxCommonDecoder::PlaybackPositionChanged);
+  nsCOMPtr<nsIRunnable> nsEvent =
+    NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
+      mObserver,
+      &MediaOmxCommonDecoder::PlaybackPositionChanged,
+      MediaDecoderEventVisibility::Observable);
   NS_DispatchToMainThread(nsEvent);
 }
 
 void AudioOffloadPlayer::NotifyAudioTearDown()
 {
   nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
       &MediaOmxCommonDecoder::AudioOffloadTearDown);
   NS_DispatchToMainThread(nsEvent);
@@ -554,18 +563,21 @@ size_t AudioOffloadPlayer::FillBuffer(vo
         CHECK(mInputBuffer->meta_data()->findInt64(
             kKeyTime, &mPositionTimeMediaUs));
       }
 
       if (refreshSeekTime) {
         if (mDispatchSeekEvents && !mSeekDuringPause) {
           mDispatchSeekEvents = false;
           AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("FillBuffer posting SEEK_COMPLETE"));
-          nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
-              &MediaDecoder::SeekingStopped);
+          nsCOMPtr<nsIRunnable> nsEvent =
+            NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
+              mObserver,
+              &MediaDecoder::SeekingStopped,
+              MediaDecoderEventVisibility::Observable);
           NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL);
 
         } else if (mSeekDuringPause) {
           // Callback is already called for seek during pause. Just reset the
           // flag
           AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Not posting seek complete as its"
               " already faked"));
           mSeekDuringPause = false;
--- a/dom/media/omx/MediaOmxCommonDecoder.cpp
+++ b/dom/media/omx/MediaOmxCommonDecoder.cpp
@@ -53,20 +53,20 @@ bool
 MediaOmxCommonDecoder::CheckDecoderCanOffloadAudio()
 {
   return (mCanOffloadAudio && !mFallbackToStateMachine && !mOutputStreams.Length() &&
       mInitialPlaybackRate == 1.0);
 }
 
 void
 MediaOmxCommonDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
-                                        bool aRestoredFromDormant)
+                                        MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MediaDecoder::FirstFrameLoaded(aInfo, aRestoredFromDormant);
+  MediaDecoder::FirstFrameLoaded(aInfo, aEventVisibility);
 
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   if (!CheckDecoderCanOffloadAudio()) {
     DECODER_LOG(PR_LOG_DEBUG, ("In %s Offload Audio check failed",
         __PRETTY_FUNCTION__));
     return;
   }
 
@@ -198,34 +198,36 @@ MediaOmxCommonDecoder::ApplyStateToState
   // something else or reset the seek time. So don't call this when audio is
   // offloaded
   if (!mAudioOffloadPlayer) {
     MediaDecoder::ApplyStateToStateMachine(aState);
   }
 }
 
 void
-MediaOmxCommonDecoder::PlaybackPositionChanged()
+MediaOmxCommonDecoder::PlaybackPositionChanged(MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mAudioOffloadPlayer) {
     MediaDecoder::PlaybackPositionChanged();
     return;
   }
 
   if (!mOwner || mShuttingDown) {
     return;
   }
 
   double lastTime = mCurrentTime;
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     mCurrentTime = mAudioOffloadPlayer->GetMediaTimeSecs();
   }
-  if (mOwner && lastTime != mCurrentTime) {
+  if (mOwner &&
+      (aEventVisibility != MediaDecoderEventVisibility::Suppressed) &&
+      lastTime != mCurrentTime) {
     FireTimeUpdate();
   }
 }
 
 void
 MediaOmxCommonDecoder::SetElementVisibility(bool aIsVisible)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/media/omx/MediaOmxCommonDecoder.h
+++ b/dom/media/omx/MediaOmxCommonDecoder.h
@@ -19,21 +19,22 @@ class AudioOffloadPlayerBase;
 class MediaOmxCommonReader;
 
 class MediaOmxCommonDecoder : public MediaDecoder
 {
 public:
   MediaOmxCommonDecoder();
 
   virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
-                                bool aRestoredFromDormant);
+                                MediaDecoderEventVisibility aEventVisibility);
   virtual void ChangeState(PlayState aState);
   virtual void ApplyStateToStateMachine(PlayState aState);
   virtual void SetVolume(double aVolume);
-  virtual void PlaybackPositionChanged();
+  virtual void PlaybackPositionChanged(MediaDecoderEventVisibility aEventVisibility =
+                                         MediaDecoderEventVisibility::Observable);
   virtual void UpdateReadyStateForData();
   virtual void SetElementVisibility(bool aIsVisible);
   virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio);
   virtual bool CheckDecoderCanOffloadAudio();
   virtual void AddOutputStream(ProcessedMediaStream* aStream,
                                bool aFinishWhenEnded);
   virtual void SetPlaybackRate(double aPlaybackRate);
 
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ b/dom/media/webaudio/BufferDecoder.cpp
@@ -136,23 +136,23 @@ BufferDecoder::IsTransportSeekable()
 
 bool
 BufferDecoder::IsMediaSeekable()
 {
   return false;
 }
 
 void
-BufferDecoder::MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags, bool aRestoredFromDormant)
+BufferDecoder::MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags, MediaDecoderEventVisibility aEventVisibility)
 {
   // ignore
 }
 
 void
-BufferDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo, bool aRestoredFromDormant)
+BufferDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo, MediaDecoderEventVisibility aEventVisibility)
 {
   // ignore
 }
 
 void
 BufferDecoder::QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags)
 {
   // ignore
--- a/dom/media/webaudio/BufferDecoder.h
+++ b/dom/media/webaudio/BufferDecoder.h
@@ -55,19 +55,22 @@ public:
 
   virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE;
   virtual layers::ImageContainer* GetImageContainer() MOZ_FINAL MOZ_OVERRIDE;
 
   virtual bool IsTransportSeekable() MOZ_FINAL MOZ_OVERRIDE;
 
   virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE;
 
-  virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags, bool aRestoredFromDormant) MOZ_FINAL MOZ_OVERRIDE;
+  virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
+                              nsAutoPtr<MetadataTags> aTags,
+                              MediaDecoderEventVisibility aEventVisibility) MOZ_FINAL MOZ_OVERRIDE;
   virtual void QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) MOZ_FINAL MOZ_OVERRIDE;
-  virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo, bool aRestoredFromDormant) MOZ_FINAL MOZ_OVERRIDE;
+  virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
+                                MediaDecoderEventVisibility aEventVisibility) MOZ_FINAL MOZ_OVERRIDE;
 
   virtual void RemoveMediaTracks() MOZ_FINAL MOZ_OVERRIDE;
 
   virtual void SetMediaEndTime(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE;
 
   virtual void UpdatePlaybackPosition(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE;
 
   virtual void OnReadMetadataCompleted() MOZ_FINAL MOZ_OVERRIDE;