Backed out 8 changesets (bug 1309516) for Win8 wpt failures in content-security-policy/media-src/media-src-7_3.html
authorPhil Ringnalda <philringnalda@gmail.com>
Fri, 28 Oct 2016 18:47:49 -0700
changeset 320102 fb72036436f98ac04e6290c0c481e01c30257e5d
parent 320072 288d92c34790593a91aadb83e0e578bdd8ec4b39
child 320103 a2e55f7205f7300d2ee4f0e4b6d943abefacc997
push id20749
push userryanvm@gmail.com
push dateSat, 29 Oct 2016 13:21:21 +0000
treeherderfx-team@1b170b39ed6b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1309516
milestone52.0a1
backs out5cb98008b3e36900ae4e786052d945c1001cb488
9ddc65900391f0bdf1c42cde846cce304dd44fef
a876261d2d38eae83fad1f84f49734ec200341ef
a154fa107dd382275c79602102a278523c0987c2
35d6e08883d65abd421ca0a2998b940fda7df965
e39be827b2200e44f51bbbf12de370ee71ed07cb
970694b0b279af4ae538998ff1900a00703c63ca
90409a433f31784609b4bcb1f421ec3963c816ed
Backed out 8 changesets (bug 1309516) for Win8 wpt failures in content-security-policy/media-src/media-src-7_3.html Backed out changeset 5cb98008b3e3 (bug 1309516) Backed out changeset 9ddc65900391 (bug 1309516) Backed out changeset a876261d2d38 (bug 1309516) Backed out changeset a154fa107dd3 (bug 1309516) Backed out changeset 35d6e08883d6 (bug 1309516) Backed out changeset e39be827b220 (bug 1309516) Backed out changeset 970694b0b279 (bug 1309516) Backed out changeset 90409a433f31 (bug 1309516)
dom/media/MediaDecoderReaderWrapper.cpp
dom/media/MediaDecoderReaderWrapper.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/MediaFormatReader.cpp
dom/media/MediaFormatReader.h
dom/media/MediaInfo.h
--- a/dom/media/MediaDecoderReaderWrapper.cpp
+++ b/dom/media/MediaDecoderReaderWrapper.cpp
@@ -4,56 +4,200 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/MozPromise.h"
 #include "MediaDecoderReaderWrapper.h"
 
 namespace mozilla {
 
+extern LazyLogModule gMediaDecoderLog;
+
+#undef LOG
+#define LOG(...) \
+  MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+// StartTimeRendezvous is a helper class that quarantines the first sample
+// until it gets a sample from both channels, such that we can be guaranteed
+// to know the start time by the time On{Audio,Video}Decoded is called on MDSM.
+class StartTimeRendezvous {
+  typedef MediaDecoderReader::MediaDataPromise MediaDataPromise;
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StartTimeRendezvous);
+
+public:
+  StartTimeRendezvous(AbstractThread* aOwnerThread,
+                      bool aHasAudio,
+                      bool aHasVideo,
+                      bool aForceZeroStartTime)
+    : mOwnerThread(aOwnerThread)
+  {
+    if (aForceZeroStartTime) {
+      mAudioStartTime.emplace(0);
+      mVideoStartTime.emplace(0);
+      return;
+    }
+    if (!aHasAudio) {
+      mAudioStartTime.emplace(INT64_MAX);
+    }
+    if (!aHasVideo) {
+      mVideoStartTime.emplace(INT64_MAX);
+    }
+  }
+
+  void Destroy()
+  {
+    mAudioStartTime = Some(mAudioStartTime.refOr(INT64_MAX));
+    mVideoStartTime = Some(mVideoStartTime.refOr(INT64_MAX));
+    mHaveStartTimePromise.RejectIfExists(false, __func__);
+  }
+
+  RefPtr<HaveStartTimePromise> AwaitStartTime()
+  {
+    if (HaveStartTime()) {
+      return HaveStartTimePromise::CreateAndResolve(true, __func__);
+    }
+    return mHaveStartTimePromise.Ensure(__func__);
+  }
+
+  template<MediaData::Type SampleType>
+  RefPtr<MediaDataPromise>
+  ProcessFirstSample(MediaData* aData)
+  {
+    typedef typename MediaDataPromise::Private PromisePrivate;
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+
+    MaybeSetChannelStartTime<SampleType>(aData->mTime);
+
+    RefPtr<PromisePrivate> p = new PromisePrivate(__func__);
+    RefPtr<MediaData> data = aData;
+    RefPtr<StartTimeRendezvous> self = this;
+    AwaitStartTime()->Then(
+      mOwnerThread, __func__,
+      [p, data, self] () {
+        MOZ_ASSERT(self->mOwnerThread->IsCurrentThreadIn());
+        p->Resolve(data, __func__);
+      },
+      [p] () {
+        p->Reject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+      });
+
+    return p.forget();
+  }
+
+  template<MediaData::Type SampleType>
+  void FirstSampleRejected(const MediaResult& aError)
+  {
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+    if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
+      LOG("StartTimeRendezvous=%p SampleType(%d) Has no samples.",
+           this, SampleType);
+      MaybeSetChannelStartTime<SampleType>(INT64_MAX);
+    } else if (aError != NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
+      mHaveStartTimePromise.RejectIfExists(false, __func__);
+    }
+  }
+
+  bool HaveStartTime() const
+  {
+    return mAudioStartTime.isSome() && mVideoStartTime.isSome();
+  }
+
+  int64_t StartTime() const
+  {
+    int64_t time = std::min(mAudioStartTime.ref(), mVideoStartTime.ref());
+    return time == INT64_MAX ? 0 : time;
+  }
+
+private:
+  ~StartTimeRendezvous() {}
+
+  template<MediaData::Type SampleType>
+  void MaybeSetChannelStartTime(int64_t aStartTime)
+  {
+    if (ChannelStartTime(SampleType).isSome()) {
+      // If we're initialized with aForceZeroStartTime=true, the channel start
+      // times are already set.
+      return;
+    }
+
+    LOG("StartTimeRendezvous=%p Setting SampleType(%d) start time to %lld",
+        this, SampleType, aStartTime);
+
+    ChannelStartTime(SampleType).emplace(aStartTime);
+    if (HaveStartTime()) {
+      mHaveStartTimePromise.ResolveIfExists(true, __func__);
+    }
+  }
+
+  Maybe<int64_t>& ChannelStartTime(MediaData::Type aType)
+  {
+    return aType == MediaData::AUDIO_DATA ? mAudioStartTime : mVideoStartTime;
+  }
+
+  MozPromiseHolder<HaveStartTimePromise> mHaveStartTimePromise;
+  RefPtr<AbstractThread> mOwnerThread;
+  Maybe<int64_t> mAudioStartTime;
+  Maybe<int64_t> mVideoStartTime;
+};
+
 MediaDecoderReaderWrapper::MediaDecoderReaderWrapper(AbstractThread* aOwnerThread,
                                                      MediaDecoderReader* aReader)
   : mForceZeroStartTime(aReader->ForceZeroStartTime())
   , mOwnerThread(aOwnerThread)
   , mReader(aReader)
 {}
 
 MediaDecoderReaderWrapper::~MediaDecoderReaderWrapper()
 {}
 
 media::TimeUnit
 MediaDecoderReaderWrapper::StartTime() const
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
-  return mStartTime.ref();
+  return media::TimeUnit::FromMicroseconds(mStartTimeRendezvous->StartTime());
 }
 
 RefPtr<MediaDecoderReaderWrapper::MetadataPromise>
 MediaDecoderReaderWrapper::ReadMetadata()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
   return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
                      &MediaDecoderReader::AsyncReadMetadata)
          ->Then(mOwnerThread, __func__, this,
                 &MediaDecoderReaderWrapper::OnMetadataRead,
                 &MediaDecoderReaderWrapper::OnMetadataNotRead)
          ->CompletionPromise();
 }
 
+RefPtr<HaveStartTimePromise>
+MediaDecoderReaderWrapper::AwaitStartTime()
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  MOZ_ASSERT(!mShutdown);
+  return mStartTimeRendezvous->AwaitStartTime();
+}
+
 void
 MediaDecoderReaderWrapper::RequestAudioData()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
 
   auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
                        &MediaDecoderReader::RequestAudioData);
 
+  if (!mStartTimeRendezvous->HaveStartTime()) {
+    p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(),
+                &StartTimeRendezvous::ProcessFirstSample<MediaData::AUDIO_DATA>,
+                &StartTimeRendezvous::FirstSampleRejected<MediaData::AUDIO_DATA>)
+         ->CompletionPromise();
+  }
+
   RefPtr<MediaDecoderReaderWrapper> self = this;
   mAudioDataRequest.Begin(p->Then(mOwnerThread, __func__,
     [self] (MediaData* aAudioSample) {
       self->mAudioDataRequest.Complete();
       aAudioSample->AdjustForStartTime(self->StartTime().ToMicroseconds());
       self->mAudioCallback.Notify(AsVariant(aAudioSample));
     },
     [self] (const MediaResult& aError) {
@@ -68,24 +212,32 @@ MediaDecoderReaderWrapper::RequestVideoD
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
 
   // Time the video decode and send this value back to callbacks who accept
   // a TimeStamp as its second parameter.
   TimeStamp videoDecodeStartTime = TimeStamp::Now();
 
-  if (aTimeThreshold.ToMicroseconds() > 0) {
+  if (aTimeThreshold.ToMicroseconds() > 0 &&
+      mStartTimeRendezvous->HaveStartTime()) {
     aTimeThreshold += StartTime();
   }
 
   auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
                        &MediaDecoderReader::RequestVideoData,
                        aSkipToNextKeyframe, aTimeThreshold.ToMicroseconds());
 
+  if (!mStartTimeRendezvous->HaveStartTime()) {
+    p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(),
+                &StartTimeRendezvous::ProcessFirstSample<MediaData::VIDEO_DATA>,
+                &StartTimeRendezvous::FirstSampleRejected<MediaData::VIDEO_DATA>)
+         ->CompletionPromise();
+  }
+
   RefPtr<MediaDecoderReaderWrapper> self = this;
   mVideoDataRequest.Begin(p->Then(mOwnerThread, __func__,
     [self, videoDecodeStartTime] (MediaData* aVideoSample) {
       self->mVideoDataRequest.Complete();
       aVideoSample->AdjustForStartTime(self->StartTime().ToMicroseconds());
       self->mVideoCallback.Notify(AsVariant(MakeTuple(aVideoSample, videoDecodeStartTime)));
     },
     [self] (const MediaResult& aError) {
@@ -217,34 +369,48 @@ MediaDecoderReaderWrapper::ResetDecode(T
 RefPtr<ShutdownPromise>
 MediaDecoderReaderWrapper::Shutdown()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mAudioDataRequest.Exists());
   MOZ_ASSERT(!mVideoDataRequest.Exists());
 
   mShutdown = true;
+  if (mStartTimeRendezvous) {
+    mStartTimeRendezvous->Destroy();
+    mStartTimeRendezvous = nullptr;
+  }
   return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
                      &MediaDecoderReader::Shutdown);
 }
 
 void
 MediaDecoderReaderWrapper::OnMetadataRead(MetadataHolder* aMetadata)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   if (mShutdown) {
     return;
   }
+  // Set up the start time rendezvous if it doesn't already exist (which is
+  // generally the case, unless we're coming out of dormant mode).
+  if (!mStartTimeRendezvous) {
+    mStartTimeRendezvous = new StartTimeRendezvous(
+      mOwnerThread, aMetadata->mInfo.HasAudio(),
+      aMetadata->mInfo.HasVideo(), mForceZeroStartTime);
 
-  if (mStartTime.isNothing()) {
-    mStartTime.emplace(aMetadata->mInfo.mStartTime);
-    // Note: MFR should be able to setup its start time by itself without going
-    // through here. MediaDecoderReader::DispatchSetStartTime() will be removed
-    // once we remove all the legacy readers' code in the following bugs.
-    mReader->DispatchSetStartTime(StartTime().ToMicroseconds());
+    RefPtr<MediaDecoderReaderWrapper> self = this;
+    mStartTimeRendezvous->AwaitStartTime()->Then(
+      mOwnerThread, __func__,
+      [self] ()  {
+        NS_ENSURE_TRUE_VOID(!self->mShutdown);
+        self->mReader->DispatchSetStartTime(self->StartTime().ToMicroseconds());
+      },
+      [] () {
+        NS_WARNING("Setting start time on reader failed");
+      });
   }
 }
 
 void
 MediaDecoderReaderWrapper::SetVideoBlankDecode(bool aIsBlankDecode)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   nsCOMPtr<nsIRunnable> r =
--- a/dom/media/MediaDecoderReaderWrapper.h
+++ b/dom/media/MediaDecoderReaderWrapper.h
@@ -12,16 +12,18 @@
 #include "mozilla/Variant.h"
 #include "nsISupportsImpl.h"
 
 #include "MediaDecoderReader.h"
 #include "MediaEventSource.h"
 
 namespace mozilla {
 
+class StartTimeRendezvous;
+
 typedef MozPromise<bool, bool, /* isExclusive = */ false> HaveStartTimePromise;
 
 typedef Variant<MediaData*, MediaResult> AudioCallbackData;
 typedef Variant<Tuple<MediaData*, TimeStamp>, MediaResult> VideoCallbackData;
 typedef Variant<MediaData::Type, WaitForDataRejectValue> WaitCallbackData;
 
 /**
  * A wrapper around MediaDecoderReader to offset the timestamps of Audio/Video
@@ -45,16 +47,17 @@ private:
   MediaCallbackExc<WaitCallbackData> mVideoWaitCallback;
 
 public:
   MediaDecoderReaderWrapper(AbstractThread* aOwnerThread,
                             MediaDecoderReader* aReader);
 
   media::TimeUnit StartTime() const;
   RefPtr<MetadataPromise> ReadMetadata();
+  RefPtr<HaveStartTimePromise> AwaitStartTime();
 
   decltype(mAudioCallback)& AudioCallback() { return mAudioCallback; }
   decltype(mVideoCallback)& VideoCallback() { return mVideoCallback; }
   decltype(mAudioWaitCallback)& AudioWaitCallback() { return mAudioWaitCallback; }
   decltype(mVideoWaitCallback)& VideoWaitCallback() { return mVideoWaitCallback; }
 
   // NOTE: please set callbacks before requesting audio/video data!
   void RequestAudioData();
@@ -125,17 +128,17 @@ private:
   MediaCallbackExc<WaitCallbackData>& WaitCallbackRef(MediaData::Type aType);
   MozPromiseRequestHolder<WaitForDataPromise>& WaitRequestRef(MediaData::Type aType);
 
   const bool mForceZeroStartTime;
   const RefPtr<AbstractThread> mOwnerThread;
   const RefPtr<MediaDecoderReader> mReader;
 
   bool mShutdown = false;
-  Maybe<media::TimeUnit> mStartTime;
+  RefPtr<StartTimeRendezvous> mStartTimeRendezvous;
 
   MozPromiseRequestHolder<MediaDataPromise> mAudioDataRequest;
   MozPromiseRequestHolder<MediaDataPromise> mVideoDataRequest;
   MozPromiseRequestHolder<WaitForDataPromise> mAudioWaitRequest;
   MozPromiseRequestHolder<WaitForDataPromise> mVideoWaitRequest;
 };
 
 } // namespace mozilla
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1247,34 +1247,54 @@ DecodeMetadataState::OnMetadataRead(Meta
   Resource()->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
 
   mMaster->mInfo = Some(aMetadata->mInfo);
   mMaster->mMetadataTags = aMetadata->mTags.forget();
 
   if (Info().mMetadataDuration.isSome()) {
     mMaster->RecomputeDuration();
   } else if (Info().mUnadjustedMetadataEndTime.isSome()) {
-    const TimeUnit unadjusted = Info().mUnadjustedMetadataEndTime.ref();
-    const TimeUnit adjustment = Info().mStartTime;
-    mMaster->mInfo->mMetadataDuration.emplace(unadjusted - adjustment);
-    mMaster->RecomputeDuration();
+    RefPtr<Master> master = mMaster;
+    Reader()->AwaitStartTime()->Then(OwnerThread(), __func__,
+      [master] () {
+        NS_ENSURE_TRUE_VOID(!master->IsShutdown());
+        auto& info = master->mInfo.ref();
+        TimeUnit unadjusted = info.mUnadjustedMetadataEndTime.ref();
+        TimeUnit adjustment = master->mReader->StartTime();
+        info.mMetadataDuration.emplace(unadjusted - adjustment);
+        master->RecomputeDuration();
+      }, [master, this] () {
+        SWARN("Adjusting metadata end time failed");
+      }
+    );
   }
 
   if (mMaster->HasVideo()) {
     SLOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d",
          Reader()->IsAsync(),
          Reader()->VideoIsHardwareAccelerated(),
          mMaster->GetAmpleVideoFrames());
   }
 
-  MOZ_ASSERT(mMaster->mDuration.Ref().isSome());
-
-  mMaster->EnqueueLoadedMetadataEvent();
-
-  if (Info().IsEncrypted() && !mMaster->mCDMProxy) {
+  // In general, we wait until we know the duration before notifying the decoder.
+  // However, we notify  unconditionally in this case without waiting for the start
+  // time, since the caller might be waiting on metadataloaded to be fired before
+  // feeding in the CDM, which we need to decode the first frame (and
+  // thus get the metadata). We could fix this if we could compute the start
+  // time by demuxing without necessaring decoding.
+  bool waitingForCDM = Info().IsEncrypted() && !mMaster->mCDMProxy;
+
+  mMaster->mNotifyMetadataBeforeFirstFrame =
+    mMaster->mDuration.Ref().isSome() || waitingForCDM;
+
+  if (mMaster->mNotifyMetadataBeforeFirstFrame) {
+    mMaster->EnqueueLoadedMetadataEvent();
+  }
+
+  if (waitingForCDM) {
     // Metadata parsing was successful but we're still waiting for CDM caps
     // to become available so that we can build the correct decryptor/decoder.
     SetState<WaitForCDMState>(mPendingDormant);
   } else if (mPendingDormant) {
     SetState<DormantState>(SeekJob{});
   } else {
     SetState<DecodingFirstFrameState>(SeekJob{});
   }
@@ -1507,35 +1527,40 @@ SeekingState::HandleSeek(SeekTarget aTar
   return SetState<SeekingState>(Move(seekJob));
 }
 
 void
 MediaDecoderStateMachine::
 SeekingState::SeekCompleted()
 {
   int64_t seekTime = mSeekTask->GetSeekTarget().GetTime().ToMicroseconds();
-  int64_t newCurrentTime;
-
-  // For the accurate seek, we always set the newCurrentTime = seekTime so that
-  // the updated HTMLMediaElement.currentTime will always be the seek target;
-  // we rely on the MediaSink to handles the gap between the newCurrentTime and
-  // the real decoded samples' start time.
-  // For the other seek types, we update the newCurrentTime with the decoded
-  // samples, set it to be the smallest start time of decoded samples.
-  if (mSeekTask->GetSeekTarget().IsAccurate()) {
+  int64_t newCurrentTime = seekTime;
+
+  // Setup timestamp state.
+  RefPtr<MediaData> video = mMaster->VideoQueue().PeekFront();
+  if (seekTime == mMaster->Duration().ToMicroseconds()) {
     newCurrentTime = seekTime;
-  } else {
+  } else if (mMaster->HasAudio()) {
     RefPtr<MediaData> audio = mMaster->AudioQueue().PeekFront();
-    RefPtr<MediaData> video = mMaster->VideoQueue().PeekFront();
-    const int64_t audioStart = audio ? audio->mTime : INT64_MAX;
-    const int64_t videoStart = video ? video->mTime : INT64_MAX;
-    newCurrentTime = std::min(audioStart, videoStart);
-    if (newCurrentTime == INT64_MAX) {
-      newCurrentTime = seekTime;
+    // Though we adjust the newCurrentTime in audio-based, and supplemented
+    // by video. For better UX, should NOT bind the slide position to
+    // the first audio data timestamp directly.
+    // While seeking to a position where there's only either audio or video, or
+    // seeking to a position lies before audio or video, we need to check if
+    // seekTime is bounded in suitable duration. See Bug 1112438.
+    int64_t audioStart = audio ? audio->mTime : seekTime;
+    // We only pin the seek time to the video start time if the video frame
+    // contains the seek time.
+    if (video && video->mTime <= seekTime && video->GetEndTime() > seekTime) {
+      newCurrentTime = std::min(audioStart, video->mTime);
+    } else {
+      newCurrentTime = audioStart;
     }
+  } else {
+    newCurrentTime = video ? video->mTime : seekTime;
   }
 
   // Change state to DECODING or COMPLETED now.
   bool isLiveStream = Resource()->IsLiveStream();
   State nextState;
   if (newCurrentTime == mMaster->Duration().ToMicroseconds() && !isLiveStream) {
     // Seeked to end of media, move to COMPLETED state. Note we don't do
     // this when playing a live stream, since the end of media will advance
@@ -1565,17 +1590,17 @@ SeekingState::SeekCompleted()
     // Otherwise we might have |newCurrentTime > mMediaSink->GetPosition()|
     // and fail the assertion in GetClock() since we didn't stop MediaSink.
     mMaster->UpdatePlaybackPositionInternal(newCurrentTime);
   }
 
   // Try to decode another frame to detect if we're at the end...
   SLOG("Seek completed, mCurrentPosition=%lld", mMaster->mCurrentPosition.Ref());
 
-  if (mMaster->VideoQueue().PeekFront()) {
+  if (video) {
     mMaster->mMediaSink->Redraw(Info().mVideo);
     mMaster->mOnPlaybackEvent.Notify(MediaEventType::Invalidate);
   }
 
   if (nextState == DECODER_STATE_COMPLETED) {
     SetState<CompletedState>();
   } else {
     SetState<DecodingState>();
@@ -1753,16 +1778,17 @@ MediaDecoderStateMachine::MediaDecoderSt
   mDecodedAudioEndTime(0),
   mDecodedVideoEndTime(0),
   mPlaybackRate(1.0),
   mLowAudioThresholdUsecs(detail::LOW_AUDIO_USECS),
   mAmpleAudioThresholdUsecs(detail::AMPLE_AUDIO_USECS),
   mAudioCaptured(false),
   INIT_WATCHABLE(mAudioCompleted, false),
   INIT_WATCHABLE(mVideoCompleted, false),
+  mNotifyMetadataBeforeFirstFrame(false),
   mMinimizePreroll(false),
   mSentLoadedMetadataEvent(false),
   mSentFirstFrameLoadedEvent(false),
   mVideoDecodeSuspended(false),
   mVideoDecodeSuspendTimer(mTaskQueue),
   mOutputStreamManager(new OutputStreamManager()),
   mResource(aDecoder->GetResource()),
   mAudioOffloading(false),
@@ -2406,20 +2432,17 @@ void MediaDecoderStateMachine::Recompute
       // any other duration sources), but the duration isn't ready yet.
       return;
     }
     // We don't fire duration changed for this case because it should have
     // already been fired on the main thread when the explicit duration was set.
     duration = TimeUnit::FromSeconds(d);
   } else if (mEstimatedDuration.Ref().isSome()) {
     duration = mEstimatedDuration.Ref().ref();
-  } else if (mInfo.isSome() && Info().mMetadataDuration.isSome()) {
-    // We need to check mInfo.isSome() because that this method might be invoked
-    // while mObservedDuration is changed which might before the metadata been
-    // read.
+  } else if (Info().mMetadataDuration.isSome()) {
     duration = Info().mMetadataDuration.ref();
   } else {
     return;
   }
 
   // Only adjust the duration when an explicit duration isn't set (MSE).
   // The duration is always exactly known with MSE and there's no need to adjust
   // it based on what may have been seen in the past; in particular as this data
@@ -2905,16 +2928,21 @@ MediaDecoderStateMachine::FinishDecodeFi
 
   DECODER_LOG("Media duration %lld, "
               "transportSeekable=%d, mediaSeekable=%d",
               Duration().ToMicroseconds(), mResource->IsTransportSeekable(), mMediaSeekable.Ref());
 
   // Get potentially updated metadata
   mReader->ReadUpdatedMetadata(mInfo.ptr());
 
+  if (!mNotifyMetadataBeforeFirstFrame) {
+    // If we didn't have duration and/or start time before, we should now.
+    EnqueueLoadedMetadataEvent();
+  }
+
   EnqueueFirstFrameLoadedEvent();
 }
 
 RefPtr<ShutdownPromise>
 MediaDecoderStateMachine::BeginShutdown()
 {
   return InvokeAsync(OwnerThread(), this, __func__,
                      &MediaDecoderStateMachine::Shutdown);
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -691,16 +691,24 @@ private:
   // the state machine thread. Synchronised via decoder monitor.
   // When data is being sent to a MediaStream, this is true when all data has
   // been written to the MediaStream.
   Watchable<bool> mAudioCompleted;
 
   // True if all video frames are already rendered.
   Watchable<bool> mVideoCompleted;
 
+  // Flag whether we notify metadata before decoding the first frame or after.
+  //
+  // Note that the odd semantics here are designed to replicate the current
+  // behavior where we notify the decoder each time we come out of dormant, but
+  // send suppressed event visibility for those cases. This code can probably be
+  // simplified.
+  bool mNotifyMetadataBeforeFirstFrame;
+
   // True if we should not decode/preroll unnecessary samples, unless we're
   // played. "Prerolling" in this context refers to when we decode and
   // buffer decoded samples in advance of when they're needed for playback.
   // This flag is set for preload=metadata media, and means we won't
   // decode more than the first video frame and first block of audio samples
   // for that media when we startup, or after a seek. When Play() is called,
   // we reset this flag, as we assume the user is playing the media, so
   // prerolling is appropriate then. This flag is used to reduce the overhead
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -349,60 +349,20 @@ MediaFormatReader::OnDemuxerInitDone(nsr
   mInfo.mMediaSeekableOnlyInBufferedRanges =
     mDemuxer->IsSeekableOnlyInBufferedRanges();
 
   if (!videoActive && !audioActive) {
     mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
     return;
   }
 
-  mTags = Move(tags);
   mInitDone = true;
-
-  // Try to get the start time.
-  // For MSE case, the start time of each track is assumed to be 0.
-  // For others, we must demux the first sample to know the start time for each
-  // track.
-  if (ForceZeroStartTime()) {
-    mAudio.mFirstDemuxedSampleTime.emplace(TimeUnit::FromMicroseconds(0));
-    mVideo.mFirstDemuxedSampleTime.emplace(TimeUnit::FromMicroseconds(0));
-  } else {
-    if (HasAudio()) {
-      RequestDemuxSamples(TrackInfo::kAudioTrack);
-    }
-
-    if (HasVideo()) {
-      RequestDemuxSamples(TrackInfo::kVideoTrack);
-    }
-  }
-
-  MaybeResolveMetadataPromise();
-}
-
-void
-MediaFormatReader::MaybeResolveMetadataPromise()
-{
-  MOZ_ASSERT(OnTaskQueue());
-
-  if ((HasAudio() && mAudio.mFirstDemuxedSampleTime.isNothing()) ||
-      (HasVideo() && mVideo.mFirstDemuxedSampleTime.isNothing())) {
-    return;
-  }
-
-  TimeUnit startTime =
-    std::min(mAudio.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()),
-             mVideo.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()));
-
-  if (!startTime.IsInfinite()) {
-    mInfo.mStartTime = startTime; // mInfo.mStartTime is initialized to 0.
-  }
-
   RefPtr<MetadataHolder> metadata = new MetadataHolder();
   metadata->mInfo = mInfo;
-  metadata->mTags = mTags->Count() ? mTags.release() : nullptr;
+  metadata->mTags = tags->Count() ? tags.release() : nullptr;
   mMetadataPromise.Resolve(metadata, __func__);
 }
 
 bool
 MediaFormatReader::IsEncrypted() const
 {
   return (HasAudio() && mInfo.mAudio.mCrypto.mValid) ||
          (HasVideo() && mInfo.mVideo.mCrypto.mValid);
@@ -635,32 +595,20 @@ MediaFormatReader::OnDemuxFailed(TrackTy
       NotifyError(aTrack, aError);
       break;
   }
 }
 
 void
 MediaFormatReader::DoDemuxVideo()
 {
-  auto p = mVideo.mTrackDemuxer->GetSamples(1);
-
-  if (mVideo.mFirstDemuxedSampleTime.isNothing()) {
-    RefPtr<MediaFormatReader> self = this;
-    p = p->Then(OwnerThread(), __func__,
-                [self] (RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples) {
-                  self->OnFirstDemuxCompleted(TrackInfo::kVideoTrack, aSamples);
-                },
-                [self] (const MediaResult& aError) {
-                  self->OnFirstDemuxFailed(TrackInfo::kVideoTrack, aError);
-                })->CompletionPromise();
-  }
-
-  mVideo.mDemuxRequest.Begin(p->Then(OwnerThread(), __func__, this,
-                                     &MediaFormatReader::OnVideoDemuxCompleted,
-                                     &MediaFormatReader::OnVideoDemuxFailed));
+  mVideo.mDemuxRequest.Begin(mVideo.mTrackDemuxer->GetSamples(1)
+                      ->Then(OwnerThread(), __func__, this,
+                             &MediaFormatReader::OnVideoDemuxCompleted,
+                             &MediaFormatReader::OnVideoDemuxFailed));
 }
 
 void
 MediaFormatReader::OnVideoDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
 {
   LOGV("%d video samples demuxed (sid:%d)",
        aSamples->mSamples.Length(),
        aSamples->mSamples[0]->mTrackInfo ? aSamples->mSamples[0]->mTrackInfo->GetID() : 0);
@@ -705,32 +653,20 @@ MediaFormatReader::RequestAudioData()
   ScheduleUpdate(TrackInfo::kAudioTrack);
 
   return p;
 }
 
 void
 MediaFormatReader::DoDemuxAudio()
 {
-  auto p = mAudio.mTrackDemuxer->GetSamples(1);
-
-  if (mAudio.mFirstDemuxedSampleTime.isNothing()) {
-    RefPtr<MediaFormatReader> self = this;
-    p = p->Then(OwnerThread(), __func__,
-                [self] (RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples) {
-                  self->OnFirstDemuxCompleted(TrackInfo::kAudioTrack, aSamples);
-                },
-                [self] (const MediaResult& aError) {
-                  self->OnFirstDemuxFailed(TrackInfo::kAudioTrack, aError);
-                })->CompletionPromise();
-  }
-
-  mAudio.mDemuxRequest.Begin(p->Then(OwnerThread(), __func__, this,
-                                     &MediaFormatReader::OnAudioDemuxCompleted,
-                                     &MediaFormatReader::OnAudioDemuxFailed));
+  mAudio.mDemuxRequest.Begin(mAudio.mTrackDemuxer->GetSamples(1)
+                      ->Then(OwnerThread(), __func__, this,
+                             &MediaFormatReader::OnAudioDemuxCompleted,
+                             &MediaFormatReader::OnAudioDemuxFailed));
 }
 
 void
 MediaFormatReader::OnAudioDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
 {
   LOGV("%d audio samples demuxed (sid:%d)",
        aSamples->mSamples.Length(),
        aSamples->mSamples[0]->mTrackInfo ? aSamples->mSamples[0]->mTrackInfo->GetID() : 0);
@@ -1018,16 +954,21 @@ MediaFormatReader::HandleDemuxedSamples(
     NotifyError(aTrack, rv);
     return;
   }
 
   if (!EnsureDecoderInitialized(aTrack)) {
     return;
   }
 
+  if (!ForceZeroStartTime() && decoder.mFirstDemuxedSampleTime.isNothing()) {
+    decoder.mFirstDemuxedSampleTime.emplace(
+      media::TimeUnit::FromMicroseconds(decoder.mQueuedSamples[0]->mTime));
+  }
+
   LOGV("Giving %s input to decoder", TrackTypeToStr(aTrack));
 
   // Decode all our demuxed frames.
   bool samplesPending = false;
   while (decoder.mQueuedSamples.Length()) {
     RefPtr<MediaRawData> sample = decoder.mQueuedSamples[0];
     RefPtr<SharedTrackInfo> info = sample->mTrackInfo;
 
@@ -1706,18 +1647,40 @@ MediaFormatReader::Seek(SeekTarget aTarg
   return p;
 }
 
 void
 MediaFormatReader::SetSeekTarget(const SeekTarget& aTarget)
 {
   MOZ_ASSERT(OnTaskQueue());
 
-  mOriginalSeekTarget = aTarget;
-  mFallbackSeekTime = mPendingSeekTime = Some(aTarget.GetTime());
+  SeekTarget target = aTarget;
+
+  // Transform the seek target time to the demuxer timeline.
+  if (!ForceZeroStartTime()) {
+    target.SetTime(aTarget.GetTime() - TimeUnit::FromMicroseconds(StartTime())
+                   + DemuxStartTime());
+  }
+
+  mOriginalSeekTarget = target;
+  mFallbackSeekTime = mPendingSeekTime = Some(target.GetTime());
+}
+
+TimeUnit
+MediaFormatReader::DemuxStartTime()
+{
+  MOZ_ASSERT(OnTaskQueue());
+  MOZ_ASSERT(!ForceZeroStartTime());
+  MOZ_ASSERT(HasAudio() || HasVideo());
+
+  const TimeUnit startTime =
+    std::min(mAudio.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()),
+             mVideo.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()));
+
+  return startTime.IsInfinite() ? TimeUnit::FromMicroseconds(0) : startTime;
 }
 
 void
 MediaFormatReader::ScheduleSeek()
 {
   if (mSeekScheduled) {
     return;
   }
@@ -2123,42 +2086,9 @@ MediaFormatReader::SetBlankDecode(TrackT
   decoder.mIsBlankDecode = aIsBlankDecode;
   decoder.Flush();
   decoder.ShutdownDecoder();
   ScheduleUpdate(TrackInfo::kVideoTrack);
 
   return;
 }
 
-void
-MediaFormatReader::OnFirstDemuxCompleted(TrackInfo::TrackType aType,
-                                         RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
-{
-  MOZ_ASSERT(OnTaskQueue());
-
-  if (mShutdown) {
-    return;
-  }
-
-  auto& decoder = GetDecoderData(aType);
-  MOZ_ASSERT(decoder.mFirstDemuxedSampleTime.isNothing());
-  decoder.mFirstDemuxedSampleTime.emplace(
-    TimeUnit::FromMicroseconds(aSamples->mSamples[0]->mTime));
-  MaybeResolveMetadataPromise();
-}
-
-void
-MediaFormatReader::OnFirstDemuxFailed(TrackInfo::TrackType aType,
-                                      const MediaResult& aError)
-{
-  MOZ_ASSERT(OnTaskQueue());
-
-  if (mShutdown) {
-    return;
-  }
-
-  auto& decoder = GetDecoderData(aType);
-  MOZ_ASSERT(decoder.mFirstDemuxedSampleTime.isNothing());
-  decoder.mFirstDemuxedSampleTime.emplace(TimeUnit::FromInfinity());
-  MaybeResolveMetadataPromise();
-}
-
 } // namespace mozilla
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -544,16 +544,17 @@ private:
   // Set to true if any of our track buffers may be blocking.
   bool mTrackDemuxersMayBlock;
 
   // Set the demuxed-only flag.
   Atomic<bool> mDemuxOnly;
 
   // Seeking objects.
   void SetSeekTarget(const SeekTarget& aTarget);
+  media::TimeUnit DemuxStartTime();
   bool IsSeeking() const { return mPendingSeekTime.isSome(); }
   bool IsVideoSeeking() const
   {
     return IsSeeking() && mOriginalSeekTarget.IsVideoOnly();
   }
   void ScheduleSeek();
   void AttemptSeek();
   void OnSeekFailed(TrackType aTrack, const MediaResult& aError);
@@ -575,22 +576,13 @@ private:
   RefPtr<VideoFrameContainer> mVideoFrameContainer;
   layers::ImageContainer* GetImageContainer();
 
   RefPtr<CDMProxy> mCDMProxy;
 
   RefPtr<GMPCrashHelper> mCrashHelper;
 
   void SetBlankDecode(TrackType aTrack, bool aIsBlankDecode);
-
-  void OnFirstDemuxCompleted(TrackInfo::TrackType aType,
-                             RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples);
-
-  void OnFirstDemuxFailed(TrackInfo::TrackType aType, const MediaResult& aError);
-
-  void MaybeResolveMetadataPromise();
-
-  UniquePtr<MetadataTags> mTags;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -498,20 +498,16 @@ public:
 
   // True if the media is seekable (i.e. supports random access).
   bool mMediaSeekable = true;
 
   // True if the media is only seekable within its buffered ranges.
   bool mMediaSeekableOnlyInBufferedRanges = false;
 
   EncryptionInfo mCrypto;
-
-  // The minimum of start times of audio and video tracks.
-  // Use to map the zero time on the media timeline to the first frame.
-  media::TimeUnit mStartTime;
 };
 
 class SharedTrackInfo {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedTrackInfo)
 public:
   SharedTrackInfo(const TrackInfo& aOriginal, uint32_t aStreamID)
     : mInfo(aOriginal.Clone())
     , mStreamSourceID(aStreamID)