Bug 1362165: P7. Only complete appendBuffer once readyState has changed. r=jwwang
authorJean-Yves Avenard <jyavenard@mozilla.com>
Tue, 09 May 2017 12:23:11 +0200
changeset 409399 846a4761d0cde831bffe4301827fd566e9bf5caf
parent 409398 55f7558b7c52281d3d8fc5e1cea26963b0cb2585
child 409400 be4b872ba9092c86378faacc94c19c93ad8a5c84
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwwang
bugs1362165
milestone55.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 1362165: P7. Only complete appendBuffer once readyState has changed. r=jwwang MSE specs require that the readyState be modified during either the Initialization Segment Received or the Coded Frame Processing algorithms. At this stage, we only handle the Initialization Segment part (readyState moving from HAVE_NOTHING to HAVE_METADATA) MozReview-Commit-ID: KBnnWuHJ6Om
dom/html/HTMLMediaElement.cpp
dom/media/mediasource/MediaSource.cpp
dom/media/mediasource/MediaSource.h
dom/media/mediasource/SourceBuffer.cpp
dom/media/mediasource/SourceBuffer.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1653,16 +1653,19 @@ HTMLMediaElement::OnChannelRedirect(nsIC
   return mChannelLoader->Redirect(aChannel, aNewChannel, aFlags);
 }
 
 void HTMLMediaElement::ShutdownDecoder()
 {
   RemoveMediaElementFromURITable();
   NS_ASSERTION(mDecoder, "Must have decoder to shut down");
   mWaitingForKeyListener.DisconnectIfExists();
+  if (mMediaSource) {
+    mMediaSource->CompletePendingTransactions();
+  }
   mDecoder->Shutdown();
   mDecoder = nullptr;
 }
 
 void HTMLMediaElement::AbortExistingLoads()
 {
   // If there is no existing decoder then we don't have anything to
   // report. This prevents reporting the initial load from an
@@ -5578,16 +5581,22 @@ HTMLMediaElement::UpdateReadyStateIntern
       mediaInfo.EnableAudio();
     }
     if (hasVideoTracks) {
       mediaInfo.EnableVideo();
     }
     MetadataLoaded(&mediaInfo, nsAutoPtr<const MetadataTags>(nullptr));
   }
 
+  if (mMediaSource) {
+    // readyState has changed, assuming it's following the pending mediasource
+    // operations. Notify the Mediasource that the operations have completed.
+    mMediaSource->CompletePendingTransactions();
+  }
+
   enum NextFrameStatus nextFrameStatus = NextFrameStatus();
   if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE ||
       (nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING &&
        mWaitingForKey == WAITING_FOR_KEY)) {
     if (mWaitingForKey != NOT_WAITING_FOR_KEY) {
       // http://w3c.github.io/encrypted-media/#wait-for-key
       // Continuing 7.3.4 Queue a "waitingforkey" Event
       // 4. Queue a task to fire a simple event named waitingforkey
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -7,16 +7,17 @@
 #include "MediaSource.h"
 
 #include "AsyncEventRunner.h"
 #include "DecoderTraits.h"
 #include "Benchmark.h"
 #include "DecoderDoctorDiagnostics.h"
 #include "MediaContainerType.h"
 #include "MediaResult.h"
+#include "MediaSourceDemuxer.h"
 #include "MediaSourceUtils.h"
 #include "SourceBuffer.h"
 #include "SourceBufferList.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/HTMLMediaElement.h"
@@ -250,31 +251,59 @@ MediaSource::AddSourceBuffer(const nsASt
     aRv.Throw(NS_ERROR_FAILURE); // XXX need a better error here
     return nullptr;
   }
   mSourceBuffers->Append(sourceBuffer);
   MSE_DEBUG("sourceBuffer=%p", sourceBuffer.get());
   return sourceBuffer.forget();
 }
 
-void
+RefPtr<MediaSource::ActiveCompletionPromise>
 MediaSource::SourceBufferIsActive(SourceBuffer* aSourceBuffer)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mActiveSourceBuffers->ClearSimple();
+  bool initMissing = false;
   bool found = false;
   for (uint32_t i = 0; i < mSourceBuffers->Length(); i++) {
     SourceBuffer* sourceBuffer = mSourceBuffers->IndexedGetter(i, found);
     MOZ_ALWAYS_TRUE(found);
     if (sourceBuffer == aSourceBuffer) {
       mActiveSourceBuffers->Append(aSourceBuffer);
     } else if (sourceBuffer->IsActive()) {
       mActiveSourceBuffers->AppendSimple(sourceBuffer);
+    } else {
+      // Some source buffers haven't yet received an init segment.
+      // There's nothing more we can do at this stage.
+      initMissing = true;
     }
   }
+  if (initMissing || !mDecoder) {
+    return ActiveCompletionPromise::CreateAndResolve(true, __func__);
+  }
+
+  mDecoder->NotifyInitDataArrived();
+
+  // Add our promise to the queue.
+  // It will be resolved once the HTMLMediaElement modifies its readyState.
+  MozPromiseHolder<ActiveCompletionPromise> holder;
+  RefPtr<ActiveCompletionPromise> promise = holder.Ensure(__func__);
+  mCompletionPromises.AppendElement(Move(holder));
+  return promise;
+}
+
+void
+MediaSource::CompletePendingTransactions()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MSE_DEBUG("Resolving %u promises", unsigned(mCompletionPromises.Length()));
+  for (auto& promise : mCompletionPromises) {
+    promise.Resolve(true, __func__);
+  }
+  mCompletionPromises.Clear();
 }
 
 void
 MediaSource::RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   SourceBuffer* sourceBuffer = &aSourceBuffer;
   MSE_API("RemoveSourceBuffer(aSourceBuffer=%p)", sourceBuffer);
@@ -428,16 +457,17 @@ MediaSource::Attach(MediaSourceDecoder* 
   SetReadyState(MediaSourceReadyState::Open);
   return true;
 }
 
 void
 MediaSource::Detach()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(mCompletionPromises.IsEmpty());
   MSE_DEBUG("mDecoder=%p owner=%p",
             mDecoder.get(), mDecoder ? mDecoder->GetOwner() : nullptr);
   if (!mDecoder) {
     MOZ_ASSERT(mReadyState == MediaSourceReadyState::Closed);
     MOZ_ASSERT(mActiveSourceBuffers->IsEmpty() && mSourceBuffers->IsEmpty());
     return;
   }
   mMediaElement = nullptr;
--- a/dom/media/mediasource/MediaSource.h
+++ b/dom/media/mediasource/MediaSource.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_dom_MediaSource_h_
 #define mozilla_dom_MediaSource_h_
 
 #include "MediaSourceDecoder.h"
 #include "js/RootingAPI.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/MozPromise.h"
 #include "mozilla/dom/MediaSourceBinding.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionNoteChild.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsID.h"
 #include "nsISupports.h"
 #include "nscore.h"
 #include "TimeUnits.h"
@@ -114,16 +115,19 @@ public:
     return mLiveSeekableRange.value();
   }
 
   AbstractThread* AbstractMainThread() const
   {
     return mAbstractMainThread;
   }
 
+  // Resolve all CompletionPromise pending.
+  void CompletePendingTransactions();
+
 private:
   // SourceBuffer uses SetDuration and SourceBufferIsActive
   friend class mozilla::dom::SourceBuffer;
 
   ~MediaSource();
 
   explicit MediaSource(nsPIDOMWindowInner* aWindow);
 
@@ -131,34 +135,41 @@ private:
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   void DurationChange(double aNewDuration, ErrorResult& aRv);
 
   // SetDuration with no checks.
   void SetDuration(double aDuration);
 
+  typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true>
+    ActiveCompletionPromise;
   // Mark SourceBuffer as active and rebuild ActiveSourceBuffers.
-  void SourceBufferIsActive(SourceBuffer* aSourceBuffer);
+  // Return a MozPromise that will be resolved once all related operations are
+  // completed, or can't progress any further.
+  // Such as, transition of readyState from HAVE_NOTHING to HAVE_METADATA.
+  RefPtr<ActiveCompletionPromise> SourceBufferIsActive(
+    SourceBuffer* aSourceBuffer);
 
   RefPtr<SourceBufferList> mSourceBuffers;
   RefPtr<SourceBufferList> mActiveSourceBuffers;
 
   RefPtr<MediaSourceDecoder> mDecoder;
   // Ensures the media element remains alive to dispatch progress and
   // durationchanged events.
   RefPtr<HTMLMediaElement> mMediaElement;
 
   RefPtr<nsIPrincipal> mPrincipal;
 
   const RefPtr<AbstractThread> mAbstractMainThread;
 
   MediaSourceReadyState mReadyState;
 
   Maybe<media::TimeInterval> mLiveSeekableRange;
+  nsTArray<MozPromiseHolder<ActiveCompletionPromise>> mCompletionPromises;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(MediaSource, MOZILLA_DOM_MEDIASOURCE_IMPLEMENTATION_IID)
 
 } // namespace dom
 
 } // namespace mozilla
 
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -207,16 +207,17 @@ SourceBuffer::Abort(ErrorResult& aRv)
   mCurrentAttributes.SetAppendWindowStart(0);
   mCurrentAttributes.SetAppendWindowEnd(PositiveInfinity<double>());
 }
 
 void
 SourceBuffer::AbortBufferAppend()
 {
   if (mUpdating) {
+    mCompletionPromise.DisconnectIfExists();
     if (mPendingAppend.Exists()) {
       mPendingAppend.Disconnect();
       mTrackBuffersManager->AbortAppendData();
     }
     AbortUpdating();
   }
 }
 
@@ -428,32 +429,42 @@ void
 SourceBuffer::AppendDataCompletedWithSuccess(const SourceBufferTask::AppendBufferResult& aResult)
 {
   MOZ_ASSERT(mUpdating);
   mPendingAppend.Complete();
 
   if (aResult.first()) {
     if (!mActive) {
       mActive = true;
-      mMediaSource->SourceBufferIsActive(this);
-      mMediaSource->GetDecoder()->NotifyInitDataArrived();
+      MSE_DEBUG("Init segment received");
+      RefPtr<SourceBuffer> self = this;
+      mMediaSource->SourceBufferIsActive(this)
+        ->Then(mAbstractMainThread, __func__,
+               [self, this]() {
+                 MSE_DEBUG("Complete AppendBuffer operation");
+                 mCompletionPromise.Complete();
+                 StopUpdating();
+               })
+        ->Track(mCompletionPromise);
     }
   }
   if (mActive) {
     // Tell our parent decoder that we have received new data.
     mMediaSource->GetDecoder()->NotifyDataArrived();
     // Send progress event.
     mMediaSource->GetDecoder()->NotifyBytesDownloaded();
   }
 
   mCurrentAttributes = aResult.second();
 
   CheckEndTime();
 
-  StopUpdating();
+  if (!mCompletionPromise.Exists()) {
+    StopUpdating();
+  }
 }
 
 void
 SourceBuffer::AppendDataErrored(const MediaResult& aError)
 {
   MOZ_ASSERT(mUpdating);
   mPendingAppend.Complete();
 
--- a/dom/media/mediasource/SourceBuffer.h
+++ b/dom/media/mediasource/SourceBuffer.h
@@ -177,15 +177,17 @@ private:
 
   mozilla::Atomic<bool> mActive;
 
   MozPromiseRequestHolder<SourceBufferTask::AppendPromise> mPendingAppend;
   MozPromiseRequestHolder<SourceBufferTask::RangeRemovalPromise> mPendingRemoval;
   const MediaContainerType mType;
 
   RefPtr<TimeRanges> mBuffered;
+
+  MozPromiseRequestHolder<MediaSource::ActiveCompletionPromise> mCompletionPromise;
 };
 
 } // namespace dom
 
 } // namespace mozilla
 
 #endif /* mozilla_dom_SourceBuffer_h_ */