Bug 654787 - part6: Correct the playback position while looping; r=jwwang draft
authorChun-Min Chang <chun.m.chang@gmail.com>
Fri, 24 Nov 2017 10:28:42 +0800
changeset 702946 c077338eb810dad0bcbe27db2b6663bd0b05fac2
parent 702945 dd1ef89167825da69369a1c9c8d90241c00304aa
child 702947 09e395f88f355d64b4ece8f980540bec03fb80f6
push id90643
push userbmo:cchang@mozilla.com
push dateFri, 24 Nov 2017 02:29:17 +0000
reviewersjwwang
bugs654787
milestone59.0a1
Bug 654787 - part6: Correct the playback position while looping; r=jwwang MozReview-Commit-ID: 4h2zgtbVBVq
dom/media/MediaDecoderStateMachine.cpp
dom/media/ReaderProxy.cpp
dom/media/ReaderProxy.h
dom/media/TimeUnits.h
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1934,16 +1934,18 @@ public:
 
     // StopPlayback in order to reset the IsPlaying() state so audio
     // is restarted correctly.
     mMaster->StopPlayback();
 
     if (!mSentPlaybackEndedEvent) {
       auto clockTime =
         std::max(mMaster->AudioEndTime(), mMaster->VideoEndTime());
+      // Correct the time over the end once looping was turned on.
+      Reader()->AdjustByLooping(clockTime);
       if (mMaster->mDuration.Ref()->IsInfinite()) {
         // We have a finite duration when playback reaches the end.
         mMaster->mDuration = Some(clockTime);
       }
       mMaster->UpdatePlaybackPosition(clockTime);
 
       // Ensure readyState is updated before firing the 'ended' event.
       mMaster->mOnNextFrameStatus.Notify(
@@ -3467,29 +3469,35 @@ MediaDecoderStateMachine::UpdatePlayback
     return;
   }
 
   // Cap the current time to the larger of the audio and video end time.
   // This ensures that if we're running off the system clock, we don't
   // advance the clock to after the media end time.
   if (VideoEndTime() > TimeUnit::Zero() || AudioEndTime() > TimeUnit::Zero()) {
 
-    const auto clockTime = GetClock();
+    auto clockTime = GetClock();
+
+    // Once looping was turned on, the time is probably larger than the duration
+    // of the media track, so the time over the end should be corrected.
+    mReader->AdjustByLooping(clockTime);
+    bool loopback = clockTime < GetMediaTime();
+
     // Skip frames up to the frame at the playback position, and figure out
     // the time remaining until it's time to display the next frame and drop
     // the current frame.
     NS_ASSERTION(clockTime >= TimeUnit::Zero(), "Should have positive clock time.");
 
     // These will be non -1 if we've displayed a video frame, or played an audio
     // frame.
     auto maxEndTime = std::max(VideoEndTime(), AudioEndTime());
     auto t = std::min(clockTime, maxEndTime);
     // FIXME: Bug 1091422 - chained ogg files hit this assertion.
     //MOZ_ASSERT(t >= GetMediaTime());
-    if (t > GetMediaTime()) {
+    if (loopback || t > GetMediaTime()) {
       UpdatePlaybackPosition(t);
     }
   }
   // Note we have to update playback position before releasing the monitor.
   // Otherwise, MediaDecoder::AddOutputStream could kick in when we are outside
   // the monitor and get a staled value from GetCurrentTimeUs() which hits the
   // assertion in GetClock().
 
--- a/dom/media/ReaderProxy.cpp
+++ b/dom/media/ReaderProxy.cpp
@@ -76,16 +76,21 @@ ReaderProxy::OnAudioDataRequestFailed(co
     return AudioDataPromise::CreateAndReject(aError, __func__);
   }
 
   // The data time in the audio queue is assumed to be increased linearly,
   // so we need to add the last ending time as the offset to correct the
   // audio data time in the next round when seamless looping is enabled.
   mLoopingOffset = mLastAudioEndTime;
 
+  // Save the duration of the audio track if it hasn't been set.
+  if (!mAudioDuration.IsValid()) {
+    mAudioDuration = mLastAudioEndTime;
+  }
+
   // For seamless looping, the demuxer is sought to the beginning and then
   // keep requesting decoded data in advance, upon receiving EOS.
   // The MDSM will not be aware of the EOS and keep receiving decoded data
   // as usual while looping is on.
   RefPtr<ReaderProxy> self = this;
   RefPtr<MediaFormatReader> reader = mReader;
   ResetDecode(TrackInfo::kAudioTrack);
   return SeekInternal(SeekTarget(media::TimeUnit::Zero(), SeekTarget::Accurate))
@@ -155,16 +160,17 @@ ReaderProxy::RequestVideoData(const medi
 RefPtr<ReaderProxy::SeekPromise>
 ReaderProxy::Seek(const SeekTarget& aTarget)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   mSeamlessLoopingBlocked = true;
   // Reset the members for seamless looping if the seek is triggered outside.
   mLoopingOffset = media::TimeUnit::Zero();
   mLastAudioEndTime = media::TimeUnit::Zero();
+  mAudioDuration = media::TimeUnit::Invalid();
   return SeekInternal(aTarget);
 }
 
 RefPtr<ReaderProxy::SeekPromise>
 ReaderProxy::SeekInternal(const SeekTarget& aTarget)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   SeekTarget adjustedTarget = aTarget;
@@ -281,9 +287,20 @@ ReaderProxy::SetCanonicalDuration(
 
 void
 ReaderProxy::SetSeamlessLoopingEnabled(bool aEnabled)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   mSeamlessLoopingEnabled = aEnabled;
 }
 
+void
+ReaderProxy::AdjustByLooping(media::TimeUnit& aTime)
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  MOZ_ASSERT(!mShutdown);
+  MOZ_ASSERT(!mSeamlessLoopingEnabled || !mSeamlessLoopingBlocked);
+  if (mAudioDuration.IsValid() && mAudioDuration.IsPositive()) {
+    aTime = aTime % mAudioDuration.ToMicroseconds();
+  }
+}
+
 } // namespace mozilla
--- a/dom/media/ReaderProxy.h
+++ b/dom/media/ReaderProxy.h
@@ -81,16 +81,18 @@ public:
 
   void SetVideoBlankDecode(bool aIsBlankDecode);
 
   void SetCanonicalDuration(
     AbstractCanonical<media::NullableTimeUnit>* aCanonical);
 
   void SetSeamlessLoopingEnabled(bool aEnabled);
 
+  void AdjustByLooping(media::TimeUnit& aTime);
+
 private:
   ~ReaderProxy();
   RefPtr<MetadataPromise> OnMetadataRead(MetadataHolder&& aMetadata);
   RefPtr<MetadataPromise> OnMetadataNotRead(const MediaResult& aError);
   void UpdateDuration();
   RefPtr<SeekPromise> SeekInternal(const SeekTarget& aTarget);
 
   RefPtr<ReaderProxy::AudioDataPromise> OnAudioDataRequestCompleted(
@@ -109,16 +111,18 @@ private:
 
   // Duration, mirrored from the state machine task queue.
   Mirror<media::NullableTimeUnit> mDuration;
 
   // The total duration of audio looping in previous rounds.
   media::TimeUnit mLoopingOffset = media::TimeUnit::Zero();
   // To keep tracking the latest time of decoded audio data.
   media::TimeUnit mLastAudioEndTime = media::TimeUnit::Zero();
+  // The duration of the audio track.
+  media::TimeUnit mAudioDuration = media::TimeUnit::Invalid();
 
   // To prevent seamless looping while seeking.
   bool mSeamlessLoopingBlocked;
   // Indicates whether we should loop the media.
   bool mSeamlessLoopingEnabled;
 };
 
 } // namespace mozilla
--- a/dom/media/TimeUnits.h
+++ b/dom/media/TimeUnits.h
@@ -172,16 +172,20 @@ public:
   TimeUnit MultDouble(double aVal) const
   {
     return TimeUnit::FromSeconds(ToSeconds() * aVal);
   }
   friend TimeUnit operator/(const TimeUnit& aUnit, int aVal)
   {
     return TimeUnit(aUnit.mValue / aVal);
   }
+  friend TimeUnit operator%(const TimeUnit& aUnit, int aVal)
+  {
+    return TimeUnit(aUnit.mValue % aVal);
+  }
 
   bool IsValid() const { return mValue.isValid(); }
 
   constexpr TimeUnit()
     : mValue(CheckedInt64(0))
   {
   }