Bug 1499903 - part1 : correct the events order when we're in the seamless looping. r=chunmin
authoralwu <alwu@mozilla.com>
Fri, 23 Nov 2018 05:23:48 +0000
changeset 504216 55b9e352f46dd27c4bc820b70906d91ef88e83a0
parent 504215 a773a42f4c001614c4ae826bc8ada7de29cea3e6
child 504217 7b73044f0618cd59277967b5201fb529d58caea3
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschunmin
bugs1499903
milestone65.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 1499903 - part1 : correct the events order when we're in the seamless looping. r=chunmin When the media which has `loop` attribute is playing to the end, the spec mentions that media should do seek to the start position [1]. During seeking, the dispatched events order [2] for MediaElement should be 1. seeking 2. timeupdate 3. seeked [1] https://html.spec.whatwg.org/multipage/media.html#playing-the-media-resource:attr-media-loop-2 [2] https://html.spec.whatwg.org/multipage/media.html#seeking:dom-media-seek Differential Revision: https://phabricator.services.mozilla.com/D9324
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -150,16 +150,45 @@ class MediaMemoryTracker : public nsIMem
           MediaMemoryInfo(videoSize, audioSize, resourceSize), __func__);
       },
       [](size_t) {
         return MediaMemoryPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
       });
   }
 };
 
+// When media is looping back to the head position, the spec [1] mentions that
+// MediaElement should dispatch `seeking` first, `timeupdate`, and `seeked` in
+// the end. This guard should be created before we fire `timeupdate` so that it
+// can ensure the event order.
+// [1]
+// https://html.spec.whatwg.org/multipage/media.html#playing-the-media-resource:attr-media-loop-2
+// https://html.spec.whatwg.org/multipage/media.html#seeking:dom-media-seek
+class MOZ_RAII SeekEventsGuard {
+ public:
+  explicit SeekEventsGuard(MediaDecoderOwner* aOwner, bool aIsLoopingBack)
+      : mOwner(aOwner), mIsLoopingBack(aIsLoopingBack) {
+    MOZ_ASSERT(mOwner);
+    if (mIsLoopingBack) {
+      mOwner->SeekStarted();
+    }
+  }
+
+  ~SeekEventsGuard() {
+    MOZ_ASSERT(mOwner);
+    if (mIsLoopingBack) {
+      mOwner->SeekCompleted();
+    }
+  }
+
+ private:
+  MediaDecoderOwner* mOwner;
+  bool mIsLoopingBack;
+};
+
 StaticRefPtr<MediaMemoryTracker> MediaMemoryTracker::sUniqueInstance;
 
 RefPtr<MediaMemoryPromise>
 GetMediaMemorySizes()
 {
   return MediaMemoryTracker::GetSizes();
 }
 
@@ -388,20 +417,16 @@ MediaDecoder::~MediaDecoder() {
 void MediaDecoder::OnPlaybackEvent(MediaPlaybackEvent&& aEvent) {
   switch (aEvent.mType) {
     case MediaPlaybackEvent::PlaybackEnded:
       PlaybackEnded();
       break;
     case MediaPlaybackEvent::SeekStarted:
       SeekingStarted();
       break;
-    case MediaPlaybackEvent::Loop:
-      GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
-      GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("seeked"));
-      break;
     case MediaPlaybackEvent::Invalidate:
       Invalidate();
       break;
     case MediaPlaybackEvent::EnterVideoSuspend:
       GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("mozentervideosuspend"));
       break;
     case MediaPlaybackEvent::ExitVideoSuspend:
       GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("mozexitvideosuspend"));
@@ -768,22 +793,21 @@ void MediaDecoder::NotifyPrincipalChange
   mMediaPrincipalHandle = MakePrincipalHandle(newPrincipal);
   GetOwner()->NotifyDecoderPrincipalChanged();
 }
 
 void MediaDecoder::OnSeekResolved() {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
   AbstractThread::AutoEnter context(AbstractMainThread());
-  mSeekRequest.Complete();
-
   mLogicallySeeking = false;
 
   // Ensure logical position is updated after seek.
   UpdateLogicalPositionInternal();
+  mSeekRequest.Complete();
 
   GetOwner()->SeekCompleted();
   GetOwner()->AsyncResolveSeekDOMPromiseIfExists();
 }
 
 void MediaDecoder::OnSeekRejected() {
   MOZ_ASSERT(NS_IsMainThread());
   mSeekRequest.Complete();
@@ -813,25 +837,33 @@ void MediaDecoder::ChangeState(PlayState
 
   if (mPlayState == PLAY_STATE_PLAYING) {
     GetOwner()->ConstructMediaTracks(mInfo);
   } else if (IsEnded()) {
     GetOwner()->RemoveMediaTracks();
   }
 }
 
+bool MediaDecoder::IsLoopingBack(double aPrevPos, double aCurPos) const {
+  // If current position is early than previous position and we didn't do seek,
+  // that means we looped back to the start position.
+  return mLooping && !mSeekRequest.Exists() && aCurPos < aPrevPos;
+}
+
 void MediaDecoder::UpdateLogicalPositionInternal() {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
 
   double currentPosition = CurrentPosition().ToSeconds();
   if (mPlayState == PLAY_STATE_ENDED) {
     currentPosition = std::max(currentPosition, mDuration);
   }
   bool logicalPositionChanged = mLogicalPosition != currentPosition;
+  SeekEventsGuard guard(GetOwner(),
+                        IsLoopingBack(mLogicalPosition, currentPosition));
   mLogicalPosition = currentPosition;
   DDLOG(DDLogCategory::Property, "currentTime", mLogicalPosition);
 
   // 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();
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -271,16 +271,20 @@ class MediaDecoder : public DecoderDocto
   VideoFrameContainer* GetVideoFrameContainer() { return mVideoFrameContainer; }
 
   layers::ImageContainer* GetImageContainer();
 
   // Fire timeupdate events if needed according to the time constraints
   // outlined in the specification.
   void FireTimeUpdate();
 
+  // True if we're going to loop back to the head position when media is in
+  // looping.
+  bool IsLoopingBack(double aPrevPos, double aCurPos) const;
+
   // Returns true if we can play the entire media through without stopping
   // to buffer, given the current download and playback rates.
   bool CanPlayThrough();
 
   // Called from HTMLMediaElement when owner document activity changes
   virtual void SetElementVisibility(bool aIsDocumentVisible,
                                     Visibility aElementVisibility,
                                     bool aIsElementInTree);
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -2333,26 +2333,17 @@ void MediaDecoderStateMachine::DecodingS
     mMaster->StopPlayback();
   }
 
   // Start playback if necessary so that the clock can be properly queried.
   if (!mIsPrerolling) {
     mMaster->MaybeStartPlayback();
   }
 
-  TimeUnit before = mMaster->GetMediaTime();
   mMaster->UpdatePlaybackPositionPeriodically();
-
-  // Fire the `seeking` and `seeked` events to meet the HTML spec
-  // when the media is looped back from the end to the beginning.
-  if (before > mMaster->GetMediaTime()) {
-    MOZ_ASSERT(mMaster->mLooping);
-    mMaster->mOnPlaybackEvent.Notify(MediaPlaybackEvent::Loop);
-  }
-
   MOZ_ASSERT(!mMaster->IsPlaying() || mMaster->IsStateMachineScheduled(),
              "Must have timer scheduled");
 
   MaybeStartBuffering();
 }
 
 void MediaDecoderStateMachine::DecodingState::HandleEndOfAudio() {
   AudioQueue().Finish();
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -116,17 +116,16 @@ extern LazyLogModule gMediaDecoderLog;
 
 struct MediaPlaybackEvent {
   enum EventType {
     PlaybackStarted,
     PlaybackStopped,
     PlaybackProgressed,
     PlaybackEnded,
     SeekStarted,
-    Loop,
     Invalidate,
     EnterVideoSuspend,
     ExitVideoSuspend,
     StartVideoSuspendTimer,
     CancelVideoSuspendTimer,
     VideoOnlySeekBegin,
     VideoOnlySeekCompleted,
   } mType;