Bug 1162803 - Mirror playback position. r=jww,r=sotaro
authorBobby Holley <bobbyholley@gmail.com>
Thu, 07 May 2015 17:04:22 -0700
changeset 243381 9317883773d204542df50e2a3c0ce81e75694e37
parent 243380 f2185479a70675d748207ca3702dfbbdd14305d1
child 243382 ea5522b52e14eff4b51996fe45b7dd97036d9ea8
push id28738
push usercbook@mozilla.com
push dateTue, 12 May 2015 14:11:31 +0000
treeherderautoland@bedce1b405a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjww, sotaro
bugs1162803
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 1162803 - Mirror playback position. r=jww,r=sotaro
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/StateMirroring.h
dom/media/omx/AudioOffloadPlayer.cpp
dom/media/omx/AudioOffloadPlayer.h
dom/media/omx/MediaOmxCommonDecoder.cpp
dom/media/omx/MediaOmxCommonDecoder.h
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -546,19 +546,17 @@ void MediaDecoder::AddOutputStream(Proce
   DECODER_LOG("AddOutputStream aStream=%p!", aStream);
 
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     if (mDecoderStateMachine) {
       mDecoderStateMachine->DispatchAudioCaptured();
     }
     if (!GetDecodedStream()) {
-      int64_t t = mDecoderStateMachine ?
-                  mDecoderStateMachine->GetCurrentTimeUs() : 0;
-      RecreateDecodedStream(t, aStream->Graph());
+      RecreateDecodedStream(mLogicalPosition, aStream->Graph());
     }
     OutputStreamData* os = mOutputStreams.AppendElement();
     os->Init(this, aStream);
     ConnectDecodedStreamToOutputStream(os);
     if (aFinishWhenEnded) {
       // Ensure that aStream finishes the moment mDecodedStream does.
       aStream->SetAutofinish(true);
     }
@@ -606,17 +604,18 @@ bool MediaDecoder::IsInfinite()
 
 MediaDecoder::MediaDecoder() :
   mWatchManager(this, AbstractThread::MainThread()),
   mNextFrameStatus(AbstractThread::MainThread(),
                    MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
                    "MediaDecoder::mNextFrameStatus (Mirror)"),
   mDecoderPosition(0),
   mPlaybackPosition(0),
-  mCurrentTime(0.0),
+  mLogicalPosition(0.0),
+  mCurrentPosition(AbstractThread::MainThread(), 0, "MediaDecoder::mCurrentPosition (Mirror)"),
   mVolume(AbstractThread::MainThread(), 0.0, "MediaDecoder::mVolume (Canonical)"),
   mPlaybackRate(AbstractThread::MainThread(), 1.0, "MediaDecoder::mPlaybackRate (Canonical)"),
   mPreservesPitch(AbstractThread::MainThread(), true, "MediaDecoder::mPreservesPitch (Canonical)"),
   mDuration(-1),
   mMediaSeekable(true),
   mSameOriginMedia(false),
   mReentrantMonitor("media.decoder"),
   mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING,
@@ -644,19 +643,28 @@ MediaDecoder::MediaDecoder() :
   mIsHeuristicDormant(false)
 {
   MOZ_COUNT_CTOR(MediaDecoder);
   MOZ_ASSERT(NS_IsMainThread());
   MediaMemoryTracker::AddMediaDecoder(this);
 
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
 
+  //
   // Initialize watchers.
+  //
+
+  // readyState
   mWatchManager.Watch(mPlayState, &MediaDecoder::UpdateReadyState);
   mWatchManager.Watch(mNextFrameStatus, &MediaDecoder::UpdateReadyState);
+
+  // mLogicalPosition
+  mWatchManager.Watch(mCurrentPosition, &MediaDecoder::UpdateLogicalPosition);
+  mWatchManager.Watch(mPlayState, &MediaDecoder::UpdateLogicalPosition);
+  mWatchManager.Watch(mLogicallySeeking, &MediaDecoder::UpdateLogicalPosition);
 }
 
 bool MediaDecoder::Init(MediaDecoderOwner* aOwner)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mOwner = aOwner;
   mVideoFrameContainer = aOwner->GetVideoFrameContainer();
   MediaShutdownManager::Instance().Register(this);
@@ -826,17 +834,17 @@ nsresult MediaDecoder::Seek(double aTime
   UpdateDormantState(false /* aDormantTimeout */, true /* aActivity */);
 
   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);
 
-  mCurrentTime = aTime;
+  mLogicalPosition = aTime;
   mWasEndedWhenEnteredDormant = false;
 
   mLogicallySeeking = true;
   UpdateDecodedStream();
   SeekTarget target = SeekTarget(timeUsecs, aSeekType);
   CallSeek(target);
 
   if (mPlayState == PLAY_STATE_ENDED) {
@@ -858,17 +866,17 @@ void MediaDecoder::CallSeek(const SeekTa
                                     &MediaDecoderStateMachine::Seek, aTarget)
     ->RefableThen(AbstractThread::MainThread(), __func__, this,
                   &MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected));
 }
 
 double MediaDecoder::GetCurrentTime()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  return mCurrentTime;
+  return mLogicalPosition;
 }
 
 already_AddRefed<nsIPrincipal> MediaDecoder::GetCurrentPrincipal()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mResource ? mResource->GetCurrentPrincipal() : nullptr;
 }
 
@@ -1066,17 +1074,16 @@ void MediaDecoder::PlaybackEnded()
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown ||
       mLogicallySeeking ||
       mPlayState == PLAY_STATE_LOADING) {
     return;
   }
 
-  PlaybackPositionChanged();
   ChangeState(PLAY_STATE_ENDED);
   InvalidateWithFlags(VideoFrameContainer::INVALIDATE_FORCE);
 
   if (mOwner)  {
     mOwner->PlaybackEnded();
   }
 
   // This must be called after |mOwner->PlaybackEnded()| call above, in order
@@ -1254,17 +1261,17 @@ void MediaDecoder::OnSeekResolved(SeekRe
     fireEnded = aVal.mAtEnd;
     if (aVal.mAtEnd) {
       ChangeState(PLAY_STATE_ENDED);
     }
     mLogicallySeeking = false;
     UpdateDecodedStream();
   }
 
-  PlaybackPositionChanged(aVal.mEventVisibility);
+  UpdateLogicalPosition(aVal.mEventVisibility);
 
   if (mOwner) {
     if (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed) {
       mOwner->SeekCompleted();
       if (fireEnded) {
         mOwner->PlaybackEnded();
       }
     }
@@ -1314,55 +1321,39 @@ void MediaDecoder::ChangeState(PlayState
 
   CancelDormantTimer();
   // Start dormant timer if necessary
   StartDormantTimer();
 
   GetReentrantMonitor().NotifyAll();
 }
 
-void MediaDecoder::PlaybackPositionChanged(MediaDecoderEventVisibility aEventVisibility)
+void MediaDecoder::UpdateLogicalPosition(MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown)
     return;
 
-  double lastTime = mCurrentTime;
+  // Per spec, offical position remains stable during pause and seek.
+  if (mPlayState == PLAY_STATE_PAUSED || IsSeeking()) {
+    return;
+  }
 
-  // Control the scope of the monitor so it is not
-  // held while the timeupdate and the invalidate is run.
-  {
-    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-    if (mDecoderStateMachine) {
-      // Don't update the official playback position when paused which is
-      // expected by the script. (The current playback position might be still
-      // advancing for a while after paused.)
-      if (!IsSeeking() && mPlayState != PLAY_STATE_PAUSED) {
-        // Only update the current playback position if we're not seeking.
-        // If we are seeking, the update could have been scheduled on the
-        // state machine thread while we were playing but after the seek
-        // algorithm set the current playback position on the main thread,
-        // and we don't want to override the seek algorithm and change the
-        // current time after the seek has started but before it has
-        // completed.
-        mCurrentTime = mDecoderStateMachine->GetCurrentTime();
-      }
-      mDecoderStateMachine->ClearPositionChangeFlag();
-    }
-  }
+  double currentPosition = static_cast<double>(CurrentPosition()) / static_cast<double>(USECS_PER_S);
+  bool logicalPositionChanged = mLogicalPosition != currentPosition;
+  mLogicalPosition = currentPosition;
 
   // 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 &&
-      (aEventVisibility != MediaDecoderEventVisibility::Suppressed) &&
-      lastTime != mCurrentTime) {
+  if (mOwner && logicalPositionChanged &&
+      aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
     FireTimeUpdate();
   }
 }
 
 void MediaDecoder::DurationChanged()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
@@ -1558,18 +1549,20 @@ bool MediaDecoder::OnDecodeTaskQueue() c
 void
 MediaDecoder::SetStateMachine(MediaDecoderStateMachine* aStateMachine)
 {
   MOZ_ASSERT_IF(aStateMachine, !mDecoderStateMachine);
   mDecoderStateMachine = aStateMachine;
 
   if (mDecoderStateMachine) {
     mNextFrameStatus.Connect(mDecoderStateMachine->CanonicalNextFrameStatus());
+    mCurrentPosition.Connect(mDecoderStateMachine->CanonicalCurrentPosition());
   } else {
     mNextFrameStatus.DisconnectIfConnected();
+    mCurrentPosition.DisconnectIfConnected();
   }
 }
 
 ReentrantMonitor& MediaDecoder::GetReentrantMonitor() {
   return mReentrantMonitor;
 }
 
 ImageContainer* MediaDecoder::GetImageContainer()
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -808,20 +808,18 @@ public:
     mLogicallySeeking = false;
   }
   void OnSeekResolved(SeekResolveValue aVal);
 
   // Seeking has started. Inform the element on the main
   // thread.
   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(MediaDecoderEventVisibility aEventVisibility = MediaDecoderEventVisibility::Observable);
+  void UpdateLogicalPosition(MediaDecoderEventVisibility aEventVisibility);
+  void UpdateLogicalPosition() { UpdateLogicalPosition(MediaDecoderEventVisibility::Observable); }
 
   // Find the end of the cached data starting at the current decoder
   // position.
   int64_t GetDownloadPosition();
 
   // Updates the approximate byte offset which playback has reached. This is
   // used to calculate the readyState transitions.
   void UpdatePlaybackOffset(int64_t aOffset);
@@ -1067,21 +1065,32 @@ protected:
   // back again.
   int64_t mDecoderPosition;
   // Current playback position in the stream. This is (approximately)
   // where we're up to playing back the stream. This is not adjusted
   // during decoder seek operations, but it's updated at the end when we
   // start playing back again.
   int64_t mPlaybackPosition;
 
-  // The current playback position of the media resource in units of
-  // seconds. This is updated approximately at the framerate of the
-  // video (if it is a video) or the callback period of the audio.
-  // It is read and written from the main thread only.
-  double mCurrentTime;
+  // The logical playback position of the media resource in units of
+  // seconds. This corresponds to the "official position" in HTML5. Note that
+  // we need to store this as a double, rather than an int64_t (like
+  // mCurrentPosition), so that |v.currentTime = foo; v.currentTime == foo|
+  // returns true without being affected by rounding errors.
+  double mLogicalPosition;
+
+  // The current playback position of the underlying playback infrastructure.
+  // This corresponds to the "current position" in HTML5.
+  //
+  // NB: Don't use mCurrentPosition directly, but rather CurrentPosition() below.
+  Mirror<int64_t> mCurrentPosition;
+
+  // We allow omx subclasses to substitute an alternative current position for
+  // usage with the audio offload player.
+  virtual int64_t CurrentPosition() { return mCurrentPosition; }
 
   // Volume of playback.  0.0 = muted. 1.0 = full volume.
   Canonical<double> mVolume;
 public:
   AbstractCanonical<double>* CanonicalVolume() { return &mVolume; }
 protected:
 
   // PlaybackRate and pitch preservation status we should start at.
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -76,23 +76,16 @@ extern PRLogModuleInfo* gMediaDecoderLog
 #endif
 
 // Somehow MSVC doesn't correctly delete the comma before ##__VA_ARGS__
 // when __VA_ARGS__ expands to nothing. This is a workaround for it.
 #define DECODER_WARN_HELPER(a, b) NS_WARNING b
 #define DECODER_WARN(x, ...) \
   DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoder.get(), ##__VA_ARGS__).get()))
 
-// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
-// GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
-// implementation.  With unified builds, putting this in headers is not enough.
-#ifdef GetCurrentTime
-#undef GetCurrentTime
-#endif
-
 // Certain constants get stored as member variables and then adjusted by various
 // scale factors on a per-decoder basis. We want to make sure to avoid using these
 // constants directly, so we put them in a namespace.
 namespace detail {
 
 // If audio queue has less than this many usecs of decoded audio, we won't risk
 // trying to decode the video, we'll skip decoding video up to the next
 // keyframe. We may increase this value for an individual decoder if we
@@ -218,17 +211,17 @@ MediaDecoderStateMachine::MediaDecoderSt
   mNextPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_PAUSED,
                  "MediaDecoderStateMachine::mNextPlayState (Mirror)"),
   mLogicallySeeking(mTaskQueue, false,
              "MediaDecoderStateMachine::mLogicallySeeking (Mirror)"),
   mNextFrameStatus(mTaskQueue, MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
                    "MediaDecoderStateMachine::mNextFrameStatus (Canonical)"),
   mFragmentEndTime(-1),
   mReader(aReader),
-  mCurrentPosition(0),
+  mCurrentPosition(mTaskQueue, 0, "MediaDecoderStateMachine::mCurrentPosition (Canonical)"),
   mAudioStartTime(-1),
   mAudioEndTime(-1),
   mDecodedAudioEndTime(-1),
   mVideoFrameEndTime(-1),
   mDecodedVideoEndTime(-1),
   mVolume(mTaskQueue, 1.0, "MediaDecoderStateMachine::mVolume (Mirror)"),
   mPlaybackRate(1.0),
   mLogicalPlaybackRate(mTaskQueue, 1.0, "MediaDecoderStateMachine::mLogicalPlaybackRate (Mirror)"),
@@ -1319,26 +1312,16 @@ void MediaDecoderStateMachine::UpdatePla
 }
 
 void MediaDecoderStateMachine::UpdatePlaybackPosition(int64_t aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   UpdatePlaybackPositionInternal(aTime);
 
   bool fragmentEnded = mFragmentEndTime >= 0 && GetMediaTime() >= mFragmentEndTime;
-  if (!mPositionChangeQueued || fragmentEnded) {
-    mPositionChangeQueued = true;
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
-        mDecoder,
-        &MediaDecoder::PlaybackPositionChanged,
-        MediaDecoderEventVisibility::Observable);
-    AbstractThread::MainThread()->Dispatch(event.forget());
-  }
-
   mMetadataManager.DispatchMetadataIfNeeded(mDecoder, aTime);
 
   if (fragmentEnded) {
     StopPlayback();
   }
 }
 
 void MediaDecoderStateMachine::ClearPositionChangeFlag()
@@ -1384,26 +1367,16 @@ void MediaDecoderStateMachine::VolumeCha
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mAudioSink) {
     mAudioSink->SetVolume(mVolume);
   }
 }
 
-double MediaDecoderStateMachine::GetCurrentTime() const
-{
-  return static_cast<double>(mCurrentPosition) / static_cast<double>(USECS_PER_S);
-}
-
-int64_t MediaDecoderStateMachine::GetCurrentTimeUs() const
-{
-  return mCurrentPosition;
-}
-
 bool MediaDecoderStateMachine::IsRealTime() const
 {
   return mRealTime;
 }
 
 int64_t MediaDecoderStateMachine::GetDuration()
 {
   AssertCurrentThreadInMonitor();
@@ -1454,17 +1427,17 @@ void MediaDecoderStateMachine::SetDurati
 
   if (aDuration == INT64_MAX) {
     mEndTime = -1;
     return;
   }
 
   mEndTime = mStartTime + aDuration;
 
-  if (mDecoder && mEndTime >= 0 && mEndTime < mCurrentPosition) {
+  if (mDecoder && mEndTime >= 0 && mEndTime < mCurrentPosition.ReadOnWrongThread()) {
     // The current playback position is now past the end of the element duration
     // the user agent must also seek to the time of the end of the media
     // resource.
     if (NS_IsMainThread()) {
       // Seek synchronously.
       mDecoder->Seek(double(mEndTime) / USECS_PER_S, SeekTarget::Accurate);
     } else {
       // Queue seek to new end position.
@@ -2493,17 +2466,17 @@ MediaDecoderStateMachine::SeekCompleted(
     DECODER_LOG("Changed state from SEEKING (to %lld) to DECODING", seekTime);
     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, mCurrentPosition=%lld", mCurrentPosition);
+  DECODER_LOG("Seek completed, mCurrentPosition=%lld", mCurrentPosition.Ref());
 
   // Reset quick buffering status. This ensures that if we began the
   // seek while quick-buffering, we won't bypass quick buffering mode
   // if we need to buffer after the seek.
   mQuickBuffering = false;
 
   mCurrentSeek.Resolve(mState == DECODER_STATE_COMPLETED, __func__);
   ScheduleStateMachine();
@@ -2560,16 +2533,17 @@ MediaDecoderStateMachine::FinishShutdown
   // Disconnect canonicals and mirrors before shutting down our task queue.
   mPlayState.DisconnectIfConnected();
   mNextPlayState.DisconnectIfConnected();
   mLogicallySeeking.DisconnectIfConnected();
   mVolume.DisconnectIfConnected();
   mLogicalPlaybackRate.DisconnectIfConnected();
   mPreservesPitch.DisconnectIfConnected();
   mNextFrameStatus.DisconnectAll();
+  mCurrentPosition.DisconnectAll();
 
   // Shut down the watch manager before shutting down our task queue.
   mWatchManager.Shutdown();
 
   MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
              "How did we escape from the shutdown state?");
   // We must daisy-chain these events to destroy the decoder. We must
   // destroy the decoder on the main thread, but we can't destroy the
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -94,23 +94,16 @@ hardware (via AudioStream).
 #include "StateMirroring.h"
 
 namespace mozilla {
 
 class AudioSegment;
 class MediaTaskQueue;
 class AudioSink;
 
-// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
-// GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
-// implementation.
-#ifdef GetCurrentTime
-#undef GetCurrentTime
-#endif
-
 /*
   The state machine class. This manages the decoding and seeking in the
   MediaDecoderReader on the decode task queue, and A/V sync on the shared
   state machine thread, and controls the audio "push" thread.
 
   All internal state is synchronised via the decoder monitor. State changes
   are either propagated by NotifyAll on the monitor (typically when state
   changes need to be propagated to non-state machine threads) or by scheduling
@@ -224,22 +217,16 @@ public:
   // on the appropriate threads.
   bool OnDecodeTaskQueue() const;
   bool OnTaskQueue() const;
 
   // Seeks to the decoder to aTarget asynchronously.
   // Must be called on the state machine thread.
   nsRefPtr<MediaDecoder::SeekPromise> Seek(SeekTarget aTarget);
 
-  // Returns the current playback position in seconds.
-  // Called from the main thread to get the current frame time. The decoder
-  // monitor must be obtained before calling this.
-  double GetCurrentTime() const;
-  int64_t GetCurrentTimeUs() const;
-
   // Clear the flag indicating that a playback position change event
   // is currently queued. This is called from the main thread and must
   // be called with the decode monitor held.
   void ClearPositionChangeFlag();
 
   // Update the playback position. This can result in a timeupdate event
   // and an invalidate of the frame being dispatched asynchronously if
   // there is no such event currently queued.
@@ -657,18 +644,18 @@ protected:
   // events to run the decode, or if not whether we should set the reader
   // to idle mode. This is threadsafe, and can be called from any thread.
   // The decoder monitor must be held.
   void DispatchDecodeTasksIfNeeded();
 
   // Returns the "media time". This is the absolute time which the media
   // playback has reached. i.e. this returns values in the range
   // [mStartTime, mEndTime], and mStartTime will not be 0 if the media does
-  // not start at 0. Note this is different to the value returned
-  // by GetCurrentTime(), which is in the range [0,duration].
+  // not start at 0. Note this is different than the "current playback position",
+  // which is in the range [0,duration].
   int64_t GetMediaTime() const {
     AssertCurrentThreadInMonitor();
     return mStartTime + mCurrentPosition;
   }
 
   // Returns an upper bound on the number of microseconds of audio that is
   // decoded and playable. This is the sum of the number of usecs of audio which
   // is decoded and in the reader's audio queue, and the usecs of unplayed audio
@@ -984,21 +971,23 @@ protected:
 
   // Accessed only on the state machine thread.
   // Not an nsRevocableEventPtr since we must Revoke() it well before
   // this object is destroyed, anyway.
   // Protected by decoder monitor except during the SHUTDOWN state after the
   // decoder thread has been stopped.
   nsRevocableEventPtr<WakeDecoderRunnable> mPendingWakeDecoder;
 
-  // The time of the current frame in microseconds. This is referenced from
-  // 0 which is the initial playback position. Set by the state machine
-  // thread, and read-only from the main thread to get the current
-  // time value. Synchronised via decoder monitor.
-  int64_t mCurrentPosition;
+  // The time of the current frame in microseconds, corresponding to the "current
+  // playback position" in HTML5. This is referenced from 0, which is the initial
+  // playback position.
+  Canonical<int64_t> mCurrentPosition;
+public:
+  AbstractCanonical<int64_t>* CanonicalCurrentPosition() { return &mCurrentPosition; }
+protected:
 
   // The presentation time of the first audio frame that was played in
   // microseconds. We can add this to the audio stream position to determine
   // the current audio time. Accessed on audio and state machine thread.
   // Synchronized by decoder monitor.
   int64_t mAudioStartTime;
 
   // The end time of the last audio frame that's been pushed onto the audio
--- a/dom/media/StateMirroring.h
+++ b/dom/media/StateMirroring.h
@@ -162,16 +162,19 @@ private:
     }
 
     operator const T&()
     {
       MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
       return mValue;
     }
 
+    // Temporary workaround for naughty code.
+    const T& ReadOnWrongThread() { return mValue; }
+
     void Set(const T& aNewValue)
     {
       MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
 
       if (aNewValue == mValue) {
         return;
       }
 
@@ -242,16 +245,17 @@ public:
   void DisconnectAll() { return mImpl->DisconnectAll(); }
 
   // Access to the Impl.
   operator Impl&() { return *mImpl; }
   Impl* operator&() { return mImpl; }
 
   // Access to the T.
   const T& Ref() const { return *mImpl; }
+  const T& ReadOnWrongThread() const { return mImpl->ReadOnWrongThread(); }
   operator const T&() const { return Ref(); }
   void Set(const T& aNewValue) { mImpl->Set(aNewValue); }
   Canonical& operator=(const T& aNewValue) { Set(aNewValue); return *this; }
   Canonical& operator=(const Canonical& aOther) { Set(aOther); return *this; }
   Canonical(const Canonical& aOther) = delete;
 
 private:
   nsRefPtr<Impl> mImpl;
--- a/dom/media/omx/AudioOffloadPlayer.cpp
+++ b/dom/media/omx/AudioOffloadPlayer.cpp
@@ -444,20 +444,17 @@ void AudioOffloadPlayer::NotifyAudioEOS(
   nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
       &MediaDecoder::PlaybackEnded);
   NS_DispatchToMainThread(nsEvent);
 }
 
 void AudioOffloadPlayer::NotifyPositionChanged()
 {
   nsCOMPtr<nsIRunnable> nsEvent =
-    NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
-      mObserver,
-      &MediaOmxCommonDecoder::PlaybackPositionChanged,
-      MediaDecoderEventVisibility::Observable);
+    NS_NewRunnableMethod(mObserver, &MediaOmxCommonDecoder::NotifyOffloadPlayerPositionChanged);
   NS_DispatchToMainThread(nsEvent);
 }
 
 void AudioOffloadPlayer::NotifyAudioTearDown()
 {
   // Fallback to state machine.
   // state machine's seeks will be done with
   // MediaDecoderEventVisibility::Suppressed.
--- a/dom/media/omx/AudioOffloadPlayer.h
+++ b/dom/media/omx/AudioOffloadPlayer.h
@@ -228,30 +228,30 @@ private:
 
   // Stop the audio sink if we need to play till we drain the current buffer.
   // or Pause the sink in case we should stop playing immediately
   void Pause(bool aPlayPendingSamples = false);
 
   // When audio is offloaded, application processor wakes up less frequently
   // (>1sec) But when Player UI is visible we need to update progress bar
   // atleast once in 250ms. Start a timer when player UI becomes visible or
-  // audio starts playing to send PlaybackPositionChanged events once in 250ms.
+  // audio starts playing to send UpdateLogicalPosition events once in 250ms.
   // Stop the timer when UI goes invisible or play state is not playing.
   // Also make sure timer functions are always called from main thread
   nsresult StartTimeUpdate();
   nsresult StopTimeUpdate();
 
   void WakeLockCreate();
   void WakeLockRelease();
 
   // Notify end of stream by sending PlaybackEnded event to observer
   // (i.e.MediaDecoder)
   void NotifyAudioEOS();
 
-  // Notify position changed event by sending PlaybackPositionChanged event to
+  // Notify position changed event by sending UpdateLogicalPosition event to
   // observer
   void NotifyPositionChanged();
 
   // Offloaded audio track is invalidated due to usecase change. Notify
   // MediaDecoder to re-evaluate offloading options
   void NotifyAudioTearDown();
 
   // Send information from MetaData to the HAL via GonkAudioSink
--- a/dom/media/omx/MediaOmxCommonDecoder.cpp
+++ b/dom/media/omx/MediaOmxCommonDecoder.cpp
@@ -88,19 +88,17 @@ MediaOmxCommonDecoder::FirstFrameLoaded(
     mAudioOffloadPlayer = nullptr;
     mFallbackToStateMachine = true;
     DECODER_LOG(PR_LOG_DEBUG, ("In %s Unable to start offload audio %d."
       "Switching to normal mode", __PRETTY_FUNCTION__, err));
     return;
   }
   PauseStateMachine();
   if (mLogicallySeeking) {
-    int64_t timeUsecs = 0;
-    SecondsToUsecs(mCurrentTime, timeUsecs);
-    SeekTarget target = SeekTarget(timeUsecs,
+    SeekTarget target = SeekTarget(mLogicalPosition,
                                    SeekTarget::Accurate,
                                    MediaDecoderEventVisibility::Observable);
     mSeekRequest.DisconnectIfExists();
     mSeekRequest.Begin(mAudioOffloadPlayer->Seek(target)
       ->RefableThen(AbstractThread::MainThread(), __func__, static_cast<MediaDecoder*>(this),
                     &MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected));
   }
   // Call ChangeState() to run AudioOffloadPlayer since offload state enabled
@@ -130,32 +128,29 @@ MediaOmxCommonDecoder::PauseStateMachine
   GetStateMachine()->TaskQueue()->Dispatch(event);
 }
 
 void
 MediaOmxCommonDecoder::ResumeStateMachine()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  DECODER_LOG(PR_LOG_DEBUG, ("%s current time %f", __PRETTY_FUNCTION__,
-      mCurrentTime));
+  DECODER_LOG(PR_LOG_DEBUG, ("%s current time %f", __PRETTY_FUNCTION__, mLogicalPosition));
 
   if (mShuttingDown) {
     return;
   }
 
   if (!GetStateMachine()) {
     return;
   }
 
   mFallbackToStateMachine = true;
   mAudioOffloadPlayer = nullptr;
-  int64_t timeUsecs = 0;
-  SecondsToUsecs(mCurrentTime, timeUsecs);
-  SeekTarget target = SeekTarget(timeUsecs,
+  SeekTarget target = SeekTarget(mLogicalPosition,
                                  SeekTarget::Accurate,
                                  MediaDecoderEventVisibility::Suppressed);
   // Call Seek of MediaDecoderStateMachine to suppress seek events.
   RefPtr<nsRunnable> event =
     NS_NewRunnableMethodWithArg<SeekTarget>(
       GetStateMachine(),
       &MediaDecoderStateMachine::Seek,
       target);
@@ -165,57 +160,52 @@ MediaOmxCommonDecoder::ResumeStateMachin
   ChangeState(PLAY_STATE_LOADING);
   // exit dormant state
   event =
     NS_NewRunnableMethodWithArg<bool>(
       GetStateMachine(),
       &MediaDecoderStateMachine::SetDormant,
       false);
   GetStateMachine()->TaskQueue()->Dispatch(event);
+  UpdateLogicalPosition();
 }
 
 void
 MediaOmxCommonDecoder::AudioOffloadTearDown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   DECODER_LOG(PR_LOG_DEBUG, ("%s", __PRETTY_FUNCTION__));
 
   // mAudioOffloadPlayer can be null here if ResumeStateMachine was called
   // just before because of some other error.
   if (mAudioOffloadPlayer) {
-    // Audio offload player sent tear down event. Fallback to state machine
-    PlaybackPositionChanged();
     ResumeStateMachine();
   }
 }
 
 void
 MediaOmxCommonDecoder::AddOutputStream(ProcessedMediaStream* aStream,
                                        bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mAudioOffloadPlayer) {
-    // Offload player cannot handle MediaStream. Fallback
-    PlaybackPositionChanged();
     ResumeStateMachine();
   }
 
   MediaDecoder::AddOutputStream(aStream, aFinishWhenEnded);
 }
 
 void
 MediaOmxCommonDecoder::SetPlaybackRate(double aPlaybackRate)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mAudioOffloadPlayer &&
       ((aPlaybackRate != 0.0) || (aPlaybackRate != 1.0))) {
-    // Offload player cannot handle playback rate other than 1/0. Fallback
-    PlaybackPositionChanged();
     ResumeStateMachine();
   }
 
   MediaDecoder::SetPlaybackRate(aPlaybackRate);
 }
 
 void
 MediaOmxCommonDecoder::ChangeState(PlayState aState)
@@ -246,39 +236,26 @@ MediaOmxCommonDecoder::CallSeek(const Se
   }
 
   mSeekRequest.DisconnectIfExists();
   mSeekRequest.Begin(mAudioOffloadPlayer->Seek(aTarget)
     ->RefableThen(AbstractThread::MainThread(), __func__, static_cast<MediaDecoder*>(this),
                   &MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected));
 }
 
-void
-MediaOmxCommonDecoder::PlaybackPositionChanged(MediaDecoderEventVisibility aEventVisibility)
+int64_t
+MediaOmxCommonDecoder::CurrentPosition()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mAudioOffloadPlayer) {
-    MediaDecoder::PlaybackPositionChanged();
-    return;
-  }
-
-  if (!mOwner || mShuttingDown) {
-    return;
+    return MediaDecoder::CurrentPosition();
   }
 
-  double lastTime = mCurrentTime;
-  {
-    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-    mCurrentTime = mAudioOffloadPlayer->GetMediaTimeSecs();
-  }
-  if (mOwner &&
-      (aEventVisibility != MediaDecoderEventVisibility::Suppressed) &&
-      lastTime != mCurrentTime) {
-    FireTimeUpdate();
-  }
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+  return mAudioOffloadPlayer->GetMediaTimeSecs();
 }
 
 void
 MediaOmxCommonDecoder::SetElementVisibility(bool aIsVisible)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mAudioOffloadPlayer) {
     mAudioOffloadPlayer->SetElementVisibility(aIsVisible);
--- a/dom/media/omx/MediaOmxCommonDecoder.h
+++ b/dom/media/omx/MediaOmxCommonDecoder.h
@@ -23,33 +23,34 @@ class MediaOmxCommonDecoder : public Med
 public:
   MediaOmxCommonDecoder();
 
   virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
                                 MediaDecoderEventVisibility aEventVisibility) override;
   virtual void ChangeState(PlayState aState) override;
   virtual void CallSeek(const SeekTarget& aTarget) override;
   virtual void SetVolume(double aVolume) override;
-  virtual void PlaybackPositionChanged(MediaDecoderEventVisibility aEventVisibility =
-                                         MediaDecoderEventVisibility::Observable) override;
+  virtual int64_t CurrentPosition() override;
   virtual MediaDecoderOwner::NextFrameStatus NextFrameStatus() override;
   virtual void SetElementVisibility(bool aIsVisible) override;
   virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) override;
   virtual bool CheckDecoderCanOffloadAudio() override;
   virtual void AddOutputStream(ProcessedMediaStream* aStream,
                                bool aFinishWhenEnded) override;
   virtual void SetPlaybackRate(double aPlaybackRate) override;
 
   void AudioOffloadTearDown();
 
   virtual MediaDecoderStateMachine* CreateStateMachine() override;
 
   virtual MediaOmxCommonReader* CreateReader() = 0;
   virtual MediaDecoderStateMachine* CreateStateMachineFromReader(MediaOmxCommonReader* aReader) = 0;
 
+  void NotifyOffloadPlayerPositionChanged() { UpdateLogicalPosition(); }
+
 protected:
   virtual ~MediaOmxCommonDecoder();
   void PauseStateMachine();
   void ResumeStateMachine();
 
   MediaOmxCommonReader* mReader;
 
   // Offloaded audio track