Bug 1139206 - Update AudioOffloadPlayer seek r=bholley,bwu
authorSotaro Ikeda <sikeda@mozilla.com>
Tue, 31 Mar 2015 10:02:22 -0700 (2015-03-31)
changeset 236792 1f5a169f04763eb5f4cc514119d9b2d2a5e5bfe3
parent 236791 83af1139a6d335c38edb1409f30c2d5c5196ed0b
child 236793 b182c5d8d0dffa07d69f35b2d307ac2cf72cdeb6
push id57775
push usersikeda@mozilla.com
push dateTue, 31 Mar 2015 17:02:34 +0000 (2015-03-31)
treeherdermozilla-inbound@1f5a169f0476 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley, bwu
bugs1139206
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 1139206 - Update AudioOffloadPlayer seek r=bholley,bwu
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
dom/media/omx/AudioOffloadPlayer.cpp
dom/media/omx/AudioOffloadPlayer.h
dom/media/omx/AudioOffloadPlayerBase.h
dom/media/omx/MediaOmxCommonDecoder.cpp
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1205,49 +1205,50 @@ void MediaDecoder::UpdateReadyStateForDa
   if (!mOwner || mShuttingDown || !mDecoderStateMachine) {
     return;
   }
   MediaDecoderOwner::NextFrameStatus frameStatus =
     mDecoderStateMachine->GetNextFrameStatus();
   mOwner->UpdateReadyStateForData(frameStatus);
 }
 
-void MediaDecoder::OnSeekResolvedInternal(bool aAtEnd, MediaDecoderEventVisibility aEventVisibility)
+void MediaDecoder::OnSeekResolved(SeekResolveValue aVal)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  mSeekRequest.Complete();
 
   if (mShuttingDown)
     return;
 
   bool fireEnded = false;
   bool seekWasAborted = false;
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
 
     // An additional seek was requested while the current seek was
     // in operation.
     if (mRequestedSeekTarget.IsValid()) {
       ChangeState(PLAY_STATE_SEEKING);
       seekWasAborted = true;
     } else {
       UnpinForSeek();
-      fireEnded = aAtEnd;
-      if (aAtEnd) {
+      fireEnded = aVal.mAtEnd;
+      if (aVal.mAtEnd) {
         ChangeState(PLAY_STATE_ENDED);
-      } else if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
-        ChangeState(aAtEnd ? PLAY_STATE_ENDED : mNextState);
+      } else if (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed) {
+        ChangeState(aVal.mAtEnd ? PLAY_STATE_ENDED : mNextState);
       }
     }
   }
 
-  PlaybackPositionChanged(aEventVisibility);
+  PlaybackPositionChanged(aVal.mEventVisibility);
 
   if (mOwner) {
     UpdateReadyStateForData();
-    if (!seekWasAborted && (aEventVisibility != MediaDecoderEventVisibility::Suppressed)) {
+    if (!seekWasAborted && (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed)) {
       mOwner->SeekCompleted();
       if (fireEnded) {
         mOwner->PlaybackEnded();
       }
     }
   }
 }
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -800,31 +800,17 @@ public:
   // Acquires the monitor. Call from any thread.
   virtual bool IsExpectingMoreData();
 
   // Called when the video has completed playing.
   // Call on the main thread only.
   void PlaybackEnded();
 
   void OnSeekRejected() { mSeekRequest.Complete(); }
-  void OnSeekResolvedInternal(bool aAtEnd, MediaDecoderEventVisibility aEventVisibility);
-
-  void OnSeekResolved(SeekResolveValue aVal)
-  {
-    mSeekRequest.Complete();
-    OnSeekResolvedInternal(aVal.mAtEnd, aVal.mEventVisibility);
-  }
-
-#ifdef MOZ_AUDIO_OFFLOAD
-  // Temporary hack - see bug 1139206.
-  void SimulateSeekResolvedForAudioOffload(MediaDecoderEventVisibility aEventVisibility)
-  {
-    OnSeekResolvedInternal(false, aEventVisibility);
-  }
-#endif
+  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.
--- a/dom/media/omx/AudioOffloadPlayer.cpp
+++ b/dom/media/omx/AudioOffloadPlayer.cpp
@@ -52,23 +52,20 @@ PRLogModuleInfo* gAudioOffloadPlayerLog;
 
 // maximum time in paused state when offloading audio decompression.
 // When elapsed, the AudioSink is destroyed to allow the audio DSP to power down.
 static const uint64_t OFFLOAD_PAUSE_MAX_MSECS = 60000ll;
 
 AudioOffloadPlayer::AudioOffloadPlayer(MediaOmxCommonDecoder* aObserver) :
   mStarted(false),
   mPlaying(false),
-  mSeeking(false),
   mReachedEOS(false),
-  mSeekDuringPause(false),
   mIsElementVisible(true),
   mSampleRate(0),
   mStartPosUs(0),
-  mSeekTimeUs(0),
   mPositionTimeMediaUs(-1),
   mInputBuffer(nullptr),
   mObserver(aObserver)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
 #ifdef PR_LOGGING
   if (!gAudioOffloadPlayerLog) {
@@ -194,23 +191,16 @@ status_t AudioOffloadPlayer::ChangeState
     case MediaDecoder::PLAY_STATE_PLAYING: {
       status_t err = Play();
       if (err != OK) {
         return err;
       }
       StartTimeUpdate();
     } break;
 
-    case MediaDecoder::PLAY_STATE_SEEKING: {
-      int64_t seekTimeUs
-          = mObserver->GetSeekTime();
-      SeekTo(seekTimeUs, true);
-      mObserver->ResetSeekTime();
-    } break;
-
     case MediaDecoder::PLAY_STATE_PAUSED:
     case MediaDecoder::PLAY_STATE_SHUTDOWN:
       // Just pause here during play state shutdown as well to stop playing
       // offload track immediately. Resources will be freed by
       // MediaOmxCommonDecoder
       Pause();
       break;
 
@@ -273,18 +263,22 @@ status_t AudioOffloadPlayer::Play()
 
   if (!mStarted) {
     // Last pause timed out and offloaded audio sink was reset. Start it again
     err = Start(false);
     if (err != OK) {
       return err;
     }
     // Seek to last play position only when there was no seek during last pause
-    if (!mSeeking) {
-      SeekTo(mPositionTimeMediaUs);
+    android::Mutex::Autolock autoLock(mLock);
+    if (!mSeekTarget.IsValid()) {
+      mSeekTarget = SeekTarget(mPositionTimeMediaUs,
+                               SeekTarget::Accurate,
+                               MediaDecoderEventVisibility::Suppressed);
+      DoSeek();
     }
   }
 
   if (!mPlaying) {
     CHECK(mAudioSink.get());
     err = mAudioSink->Start();
     if (err == OK) {
       mPlaying = true;
@@ -338,62 +332,64 @@ void AudioOffloadPlayer::Reset()
   mReachedEOS = false;
   mStarted = false;
   mPlaying = false;
   mStartPosUs = 0;
 
   WakeLockRelease();
 }
 
-status_t AudioOffloadPlayer::SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents)
+nsRefPtr<MediaDecoder::SeekPromise> AudioOffloadPlayer::Seek(SeekTarget aTarget)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  CHECK(mAudioSink.get());
-
   android::Mutex::Autolock autoLock(mLock);
 
-  AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("SeekTo ( %lld )", aTimeUs));
+  mSeekPromise.RejectIfExists(true, __func__);
+  mSeekTarget = aTarget;
+  nsRefPtr<MediaDecoder::SeekPromise> p = mSeekPromise.Ensure(__func__);
+  DoSeek();
+  return p;
+}
 
-  mSeeking = true;
+status_t AudioOffloadPlayer::DoSeek()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mSeekTarget.IsValid());
+  CHECK(mAudioSink.get());
+
+  AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("DoSeek ( %lld )", mSeekTarget.mTime));
+
   mReachedEOS = false;
   mPositionTimeMediaUs = -1;
-  mSeekTimeUs = aTimeUs;
-  mStartPosUs = aTimeUs;
-  mDispatchSeekEvents = aDispatchSeekEvents;
+  mStartPosUs = mSeekTarget.mTime;
 
-  if (mDispatchSeekEvents) {
+  if (!mSeekPromise.IsEmpty()) {
     nsCOMPtr<nsIRunnable> nsEvent =
       NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
         mObserver,
         &MediaDecoder::SeekingStarted,
-        MediaDecoderEventVisibility::Observable);
+        mSeekTarget.mEventVisibility);
     NS_DispatchToCurrentThread(nsEvent);
   }
 
   if (mPlaying) {
     mAudioSink->Pause();
     mAudioSink->Flush();
     mAudioSink->Start();
 
   } else {
-    mSeekDuringPause = true;
-
     if (mStarted) {
       mAudioSink->Flush();
     }
 
-    if (mDispatchSeekEvents) {
-      mDispatchSeekEvents = false;
+    if (!mSeekPromise.IsEmpty()) {
       AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Fake seek complete during pause"));
-      nsCOMPtr<nsIRunnable> nsEvent =
-        NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
-          mObserver,
-          &MediaDecoder::SimulateSeekResolvedForAudioOffload,
-          MediaDecoderEventVisibility::Observable);
-      NS_DispatchToCurrentThread(nsEvent);
+      // We do not reset mSeekTarget here.
+      MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
+      mSeekPromise.Resolve(val, __func__);
     }
   }
 
   return OK;
 }
 
 double AudioOffloadPlayer::GetMediaTimeSecs()
 {
@@ -402,18 +398,18 @@ double AudioOffloadPlayer::GetMediaTimeS
       static_cast<double>(USECS_PER_S));
 }
 
 int64_t AudioOffloadPlayer::GetMediaTimeUs()
 {
   android::Mutex::Autolock autoLock(mLock);
 
   int64_t playPosition = 0;
-  if (mSeeking) {
-    return mSeekTimeUs;
+  if (mSeekTarget.IsValid()) {
+    return mSeekTarget.mTime;
   }
   if (!mStarted) {
     return mPositionTimeMediaUs;
   }
 
   playPosition = GetOutputPlayPositionUs_l();
   if (!mReachedEOS) {
     mPositionTimeMediaUs = playPosition;
@@ -434,16 +430,22 @@ int64_t AudioOffloadPlayer::GetOutputPla
 
   // HAL position is relative to the first buffer we sent at mStartPosUs
   const int64_t renderedDuration = mStartPosUs + playedUs;
   return renderedDuration;
 }
 
 void AudioOffloadPlayer::NotifyAudioEOS()
 {
+  android::Mutex::Autolock autoLock(mLock);
+  // We do not reset mSeekTarget here.
+  if (!mSeekPromise.IsEmpty()) {
+    MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
+    mSeekPromise.Resolve(val, __func__);
+  }
   nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
       &MediaDecoder::PlaybackEnded);
   NS_DispatchToMainThread(nsEvent);
 }
 
 void AudioOffloadPlayer::NotifyPositionChanged()
 {
   nsCOMPtr<nsIRunnable> nsEvent =
@@ -451,16 +453,25 @@ void AudioOffloadPlayer::NotifyPositionC
       mObserver,
       &MediaOmxCommonDecoder::PlaybackPositionChanged,
       MediaDecoderEventVisibility::Observable);
   NS_DispatchToMainThread(nsEvent);
 }
 
 void AudioOffloadPlayer::NotifyAudioTearDown()
 {
+  // Fallback to state machine.
+  // state machine's seeks will be done with
+  // MediaDecoderEventVisibility::Suppressed.
+  android::Mutex::Autolock autoLock(mLock);
+  // We do not reset mSeekTarget here.
+  if (!mSeekPromise.IsEmpty()) {
+    MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
+    mSeekPromise.Resolve(val, __func__);
+  }
   nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
       &MediaOmxCommonDecoder::AudioOffloadTearDown);
   NS_DispatchToMainThread(nsEvent);
 }
 
 // static
 size_t AudioOffloadPlayer::AudioSinkCallback(AudioSink* aAudioSink,
                                              void* aBuffer,
@@ -501,45 +512,45 @@ size_t AudioOffloadPlayer::FillBuffer(vo
   CHECK(mAudioSink.get());
 
   if (mReachedEOS) {
     return 0;
   }
 
   size_t sizeDone = 0;
   size_t sizeRemaining = aSize;
+  int64_t seekTimeUs = -1;
   while (sizeRemaining > 0) {
     MediaSource::ReadOptions options;
-    bool refreshSeekTime = false;
-
     {
       android::Mutex::Autolock autoLock(mLock);
 
-      if (mSeeking) {
-        options.setSeekTo(mSeekTimeUs);
-        refreshSeekTime = true;
+      if (mSeekTarget.IsValid()) {
+        seekTimeUs = mSeekTarget.mTime;
+        options.setSeekTo(seekTimeUs);
 
         if (mInputBuffer) {
           mInputBuffer->release();
           mInputBuffer = nullptr;
         }
-        mSeeking = false;
       }
     }
 
     if (!mInputBuffer) {
-
       status_t err;
       err = mSource->read(&mInputBuffer, &options);
 
       CHECK((!err && mInputBuffer) || (err && !mInputBuffer));
 
       android::Mutex::Autolock autoLock(mLock);
 
       if (err != OK) {
+        if (mSeekTarget.IsValid()) {
+          mSeekTarget.Reset();
+        }
         AUDIO_OFFLOAD_LOG(PR_LOG_ERROR, ("Error while reading media source %d "
             "Ok to receive EOS error at end", err));
         if (!mReachedEOS) {
           // After seek there is a possible race condition if
           // OffloadThread is observing state_stopping_1 before
           // framesReady() > 0. Ensure sink stop is called
           // after last buffer is released. This ensures the
           // partial buffer is written to the driver before
@@ -559,51 +570,34 @@ size_t AudioOffloadPlayer::FillBuffer(vo
         break;
       }
 
       if(mInputBuffer->range_length() != 0) {
         CHECK(mInputBuffer->meta_data()->findInt64(
             kKeyTime, &mPositionTimeMediaUs));
       }
 
-      if (refreshSeekTime) {
-        if (mDispatchSeekEvents && !mSeekDuringPause) {
-          mDispatchSeekEvents = false;
+      if (mSeekTarget.IsValid() && seekTimeUs == mSeekTarget.mTime) {
+        mSeekTarget.Reset();
+        if (!mSeekPromise.IsEmpty()) {
           AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("FillBuffer posting SEEK_COMPLETE"));
-          nsCOMPtr<nsIRunnable> nsEvent =
-            NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
-              mObserver,
-              &MediaDecoder::SimulateSeekResolvedForAudioOffload,
-              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;
+          MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
+          mSeekPromise.Resolve(val, __func__);
         }
-
-        NotifyPositionChanged();
+      } else if (mSeekTarget.IsValid()) {
+        AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("seek is updated during unlocking mLock"));
+      }
 
-        // need to adjust the mStartPosUs for offload decoding since parser
-        // might not be able to get the exact seek time requested.
-        mStartPosUs = mPositionTimeMediaUs;
-        AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Adjust seek time to: %.2f",
-            mStartPosUs / 1E6));
+     NotifyPositionChanged();
 
-        // clear seek time with mLock locked and once we have valid
-        // mPositionTimeMediaUs
-        // before clearing mSeekTimeUs check if a new seek request has been
-        // received while we were reading from the source with mLock released.
-        if (!mSeeking) {
-          mSeekTimeUs = 0;
-        }
-      }
+     // need to adjust the mStartPosUs for offload decoding since parser
+     // might not be able to get the exact seek time requested.
+     mStartPosUs = mPositionTimeMediaUs;
+     AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Adjust seek time to: %.2f",
+       mStartPosUs / 1E6));
     }
 
     if (mInputBuffer->range_length() == 0) {
       mInputBuffer->release();
       mInputBuffer = nullptr;
       continue;
     }
 
--- a/dom/media/omx/AudioOffloadPlayer.h
+++ b/dom/media/omx/AudioOffloadPlayer.h
@@ -73,72 +73,58 @@ public:
     SEEK_COMPLETE
   };
 
   AudioOffloadPlayer(MediaOmxCommonDecoder* aDecoder = nullptr);
 
   ~AudioOffloadPlayer();
 
   // Caller retains ownership of "aSource".
-  void SetSource(const android::sp<MediaSource> &aSource);
+  virtual void SetSource(const android::sp<MediaSource> &aSource) override;
 
   // Start the source if it's not already started and open the AudioSink to
   // create an offloaded audio track
-  status_t Start(bool aSourceAlreadyStarted = false);
+  virtual status_t Start(bool aSourceAlreadyStarted = false) override;
+
+  virtual status_t ChangeState(MediaDecoder::PlayState aState) override;
 
-  double GetMediaTimeSecs();
+  virtual void SetVolume(double aVolume) override;
+
+  virtual double GetMediaTimeSecs() override;
 
   // To update progress bar when the element is visible
-  void SetElementVisibility(bool aIsVisible);
-
-  status_t ChangeState(MediaDecoder::PlayState aState);
-
-  void SetVolume(double aVolume);
+  virtual void SetElementVisibility(bool aIsVisible) override;;
 
   // Update ready state based on current play state. Not checking data
   // availability since offloading is currently done only when whole compressed
   // data is available
-  MediaDecoderOwner::NextFrameStatus GetNextFrameStatus();
+  virtual MediaDecoderOwner::NextFrameStatus GetNextFrameStatus() override;
+
+  virtual nsRefPtr<MediaDecoder::SeekPromise> Seek(SeekTarget aTarget) override;
 
   void TimeUpdate();
 
   // Close the audio sink, stop time updates, frees the input buffers
   void Reset();
 
 private:
   // Set when audio source is started and audioSink is initialized
   // Used only in main thread
   bool mStarted;
 
   // Set when audio sink is started. i.e. playback started
   // Used only in main thread
   bool mPlaying;
 
-  // Set when playstate is seeking and reset when FillBUffer() acknowledged
-  // seeking by seeking audio source. Used in main thread and offload
-  // callback thread, protected by Mutex mLock
-  bool mSeeking;
-
   // Once playback reached end of stream (last ~100ms), position provided by DSP
   // may be reset/corrupted. This bool is used to avoid that.
   // Used in main thread and offload callback thread, protected by Mutex
   // mLock
   bool mReachedEOS;
 
-  // Set when there is a seek request during pause.
-  // Used in main thread and offload callback thread, protected by Mutex
-  // mLock
-  bool mSeekDuringPause;
-
-  // Seek can be triggered internally or by MediaDecoder. This bool is to
-  // to track seek triggered by MediaDecoder so that we can send back
-  // SeekingStarted and SeekingStopped events.
-  // Used in main thread and offload callback thread, protected by Mutex mLock
-  bool mDispatchSeekEvents;
-
   // Set when the HTML Audio Element is visible to the user.
   // Used only in main thread
   bool mIsElementVisible;
 
   // Session id given by Android::AudioSystem and used while creating audio sink
   // Used only in main thread
   int mSessionId;
 
@@ -150,20 +136,25 @@ private:
   // different than given mSeekTimeUs, if audio source cannot find a frame at
   // that position. Store seeked position in mStartPosUs and provide
   // mStartPosUs + GetPosition() (i.e. absolute position) to
   // MediaOmxCommonDecoder
   // Used in main thread and offload callback thread, protected by Mutex
   // mLock
   int64_t mStartPosUs;
 
-  // Given seek time when there is a request to seek
+  // The target of current seek when there is a request to seek
   // Used in main thread and offload callback thread, protected by Mutex
   // mLock
-  int64_t mSeekTimeUs;
+  SeekTarget mSeekTarget;
+
+  // MediaPromise of current seek.
+  // Used in main thread and offload callback thread, protected by Mutex
+  // mLock
+  MediaPromiseHolder<MediaDecoder::SeekPromise> mSeekPromise;
 
   // Positions obtained from offlaoded tracks (DSP)
   // Used in main thread and offload callback thread, protected by Mutex
   // mLock
   int64_t mPositionTimeMediaUs;
 
   // State obtained from MediaOmxCommonDecoder. Used only in main thread
   MediaDecoder::PlayState mPlayState;
@@ -216,25 +207,25 @@ private:
   static size_t AudioSinkCallback(AudioSink *aAudioSink,
                                   void *aData,
                                   size_t aSize,
                                   void *aMe,
                                   AudioSink::cb_event_t aEvent);
 
   bool IsSeeking();
 
-  // Set mSeekTime to the given position and restart the sink. Actual seek
-  // happens in FillBuffer(). If aDispatchSeekEvents is true, send
+  // Set mSeekTarget to the given position and restart the sink. Actual seek
+  // happens in FillBuffer(). If mSeekPromise is not empty, send
   // SeekingStarted event always and SeekingStopped event when the play state is
   // paused to MediaDecoder.
   // When decoding and playing happens separately, if there is a seek during
   // pause, we can decode and keep data ready.
   // In case of offload player, no way to seek during pause. So just fake that
   // seek is done.
-  status_t SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents = false);
+  status_t DoSeek();
 
   // Start/Resume the audio sink so that callback will start being called to get
   // compressed data
   status_t Play();
 
   // 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);
--- a/dom/media/omx/AudioOffloadPlayerBase.h
+++ b/dom/media/omx/AudioOffloadPlayerBase.h
@@ -61,13 +61,15 @@ public:
 
   // Update ready state based on current play state. Not checking data
   // availability since offloading is currently done only when whole compressed
   // data is available
   virtual MediaDecoderOwner::NextFrameStatus GetNextFrameStatus()
   {
     return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
   }
+
+  virtual nsRefPtr<MediaDecoder::SeekPromise> Seek(SeekTarget aTarget) = 0;
 };
 
 } // namespace mozilla
 
 #endif // AUDIO_OFFLOAD_PLAYER_BASE_H_
--- a/dom/media/omx/MediaOmxCommonDecoder.cpp
+++ b/dom/media/omx/MediaOmxCommonDecoder.cpp
@@ -121,18 +121,19 @@ MediaOmxCommonDecoder::ResumeStateMachin
   if (!mDecoderStateMachine) {
     return;
   }
 
   mFallbackToStateMachine = true;
   mAudioOffloadPlayer = nullptr;
   int64_t timeUsecs = 0;
   SecondsToUsecs(mCurrentTime, timeUsecs);
-  mRequestedSeekTarget = SeekTarget(timeUsecs, SeekTarget::Accurate);
-
+  mRequestedSeekTarget = SeekTarget(timeUsecs,
+                                    SeekTarget::Accurate,
+                                    MediaDecoderEventVisibility::Suppressed);
   mNextState = mPlayState;
   ChangeState(PLAY_STATE_LOADING);
   // exit dormant state
   RefPtr<nsRunnable> event =
     NS_NewRunnableMethodWithArg<bool>(
       mDecoderStateMachine,
       &MediaDecoderStateMachine::SetDormant,
       false);
@@ -188,20 +189,35 @@ void
 MediaOmxCommonDecoder::ChangeState(PlayState aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Keep MediaDecoder state in sync with MediaElement irrespective of offload
   // playback so it will continue to work in normal mode when offloading fails
   // in between
   MediaDecoder::ChangeState(aState);
 
-  if (mAudioOffloadPlayer) {
-    status_t err = mAudioOffloadPlayer->ChangeState(aState);
-    if (err != OK) {
-      ResumeStateMachine();
+  if (!mAudioOffloadPlayer) {
+    return;
+  }
+
+  status_t err = mAudioOffloadPlayer->ChangeState(aState);
+  if (err != OK) {
+    ResumeStateMachine();
+    return;
+  }
+
+  switch (mPlayState) {
+    case PLAY_STATE_SEEKING:
+      mSeekRequest.Begin(mAudioOffloadPlayer->Seek(mRequestedSeekTarget)
+        ->RefableThen(NS_GetCurrentThread(), __func__, static_cast<MediaDecoder*>(this),
+                      &MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected));
+      mRequestedSeekTarget.Reset();
+      break;
+    default: {
+      break;
     }
   }
 }
 
 void
 MediaOmxCommonDecoder::ApplyStateToStateMachine(PlayState aState)
 {
   MOZ_ASSERT(NS_IsMainThread());